Browse Source

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

Imprv/abolish old config api
Yuki Takei 6 years ago
parent
commit
6f2526382a
100 changed files with 1743 additions and 2454 deletions
  1. 3 5
      .eslintrc.js
  2. 7 1
      CHANGES.md
  3. 54 0
      config/jest.config.js
  4. 1 0
      config/logger/config.dev.js
  5. 3 0
      config/migrate.js
  6. 12 9
      package.json
  7. 6 39
      resource/locales/en-US/translation.json
  8. 4 37
      resource/locales/ja/translation.json
  9. 1 1
      src/client/js/app.js
  10. 3 3
      src/client/js/components/Page/RevisionPath.jsx
  11. 12 0
      src/lib/util/mongoose-utils.js
  12. 3 2
      src/migrations/20180926134048-make-email-unique.js
  13. 13 6
      src/migrations/20180927102719-init-serverurl.js
  14. 11 8
      src/migrations/20181019114028-abolish-page-group-relation.js
  15. 43 0
      src/migrations/20190618055300-abolish-crowi-classic-auth.js
  16. 64 0
      src/migrations/20190618104011-add-config-app-installed.js
  17. 15 50
      src/server/crowi/express-init.js
  18. 129 46
      src/server/crowi/index.js
  19. 0 2
      src/server/form/admin/securityGeneral.js
  20. 0 8
      src/server/form/admin/securityGoogle.js
  21. 0 7
      src/server/form/admin/securityMechanism.js
  22. 0 2
      src/server/form/index.js
  23. 0 2
      src/server/form/register.js
  24. 2 2
      src/server/models/attachment.js
  25. 58 545
      src/server/models/config.js
  26. 1 0
      src/server/models/index.js
  27. 2 5
      src/server/models/page.js
  28. 0 5
      src/server/models/user-group-relation.js
  29. 24 24
      src/server/models/user.js
  30. 103 177
      src/server/routes/admin.js
  31. 1 1
      src/server/routes/hackmd.js
  32. 158 172
      src/server/routes/index.js
  33. 13 21
      src/server/routes/installer.js
  34. 12 124
      src/server/routes/login.js
  35. 0 67
      src/server/routes/me.js
  36. 18 15
      src/server/routes/page.js
  37. 69 0
      src/server/service/acl.js
  38. 54 0
      src/server/service/app.js
  39. 1 1
      src/server/service/config-loader.js
  40. 86 28
      src/server/service/config-manager.js
  41. 56 0
      src/server/service/customize.js
  42. 11 12
      src/server/service/file-uploader/aws.js
  43. 2 1
      src/server/service/file-uploader/gridfs.js
  44. 1 0
      src/server/service/file-uploader/index.js
  45. 2 1
      src/server/service/file-uploader/local.js
  46. 2 2
      src/server/service/file-uploader/none.js
  47. 34 0
      src/server/service/file-uploader/uploader.js
  48. 1 3
      src/server/service/global-notification.js
  49. 0 22
      src/server/service/notification.js
  50. 45 50
      src/server/service/passport.js
  51. 5 5
      src/server/service/rest-qiita-API.js
  52. 20 0
      src/server/service/slack-notification.js
  53. 23 0
      src/server/service/user-group.js
  54. 71 0
      src/server/service/xss.js
  55. 0 70
      src/server/util/googleAuth.js
  56. 5 3
      src/server/util/importer.js
  57. 15 15
      src/server/util/mailer.js
  58. 171 204
      src/server/util/middlewares.js
  59. 3 5
      src/server/util/search.js
  60. 17 16
      src/server/util/slack.js
  61. 26 174
      src/server/util/swigFunctions.js
  62. 21 21
      src/server/views/admin/app.html
  63. 11 18
      src/server/views/admin/customize.html
  64. 1 1
      src/server/views/admin/external-accounts.html
  65. 1 1
      src/server/views/admin/global-notification-detail.html
  66. 1 1
      src/server/views/admin/importer.html
  67. 1 1
      src/server/views/admin/index.html
  68. 1 1
      src/server/views/admin/markdown.html
  69. 1 1
      src/server/views/admin/notification.html
  70. 1 1
      src/server/views/admin/search.html
  71. 11 170
      src/server/views/admin/security.html
  72. 1 1
      src/server/views/admin/user-group-detail.html
  73. 1 1
      src/server/views/admin/user-groups.html
  74. 1 1
      src/server/views/admin/users.html
  75. 5 6
      src/server/views/admin/widget/passport/github.html
  76. 5 6
      src/server/views/admin/widget/passport/google-oauth.html
  77. 13 13
      src/server/views/admin/widget/passport/ldap.html
  78. 12 13
      src/server/views/admin/widget/passport/oidc.html
  79. 1 2
      src/server/views/admin/widget/passport/saml.html
  80. 5 6
      src/server/views/admin/widget/passport/twitter.html
  81. 2 2
      src/server/views/customlayout-selector/forbidden.html
  82. 2 2
      src/server/views/customlayout-selector/not_creatable.html
  83. 2 2
      src/server/views/customlayout-selector/not_found.html
  84. 2 2
      src/server/views/customlayout-selector/page.html
  85. 2 2
      src/server/views/customlayout-selector/page_list.html
  86. 2 2
      src/server/views/customlayout-selector/user_page.html
  87. 75 41
      src/server/views/installer.html
  88. 2 2
      src/server/views/invited.html
  89. 1 1
      src/server/views/layout-crowi/base/layout.html
  90. 1 1
      src/server/views/layout-growi/page.html
  91. 1 1
      src/server/views/layout-growi/user_page.html
  92. 1 1
      src/server/views/layout-growi/widget/header.html
  93. 1 1
      src/server/views/layout-kibela/page.html
  94. 1 1
      src/server/views/layout-kibela/user_page.html
  95. 1 1
      src/server/views/layout-kibela/widget/header.html
  96. 27 74
      src/server/views/layout/layout.html
  97. 27 50
      src/server/views/login.html
  98. 1 1
      src/server/views/login/error.html
  99. 1 3
      src/server/views/me/api_token.html
  100. 1 1
      src/server/views/me/external-accounts.html

+ 3 - 5
.eslintrc.js

@@ -2,10 +2,11 @@ module.exports = {
   extends: [
     'weseek',
     'weseek/react',
+    "plugin:jest/recommended",
   ],
   env: {
-    mocha: true,
     jquery: true,
+    "jest/globals": true,
   },
   globals: {
     $: true,
@@ -15,7 +16,7 @@ module.exports = {
     window: true,
   },
   plugins: [
-    'chai-friendly',
+    "jest",
   ],
   rules: {
     'indent': [
@@ -35,8 +36,5 @@ module.exports = {
     ],
     // eslint-plugin-import rules
     'import/no-unresolved': [2, { ignore: ['^@'] }], // ignore @alias/..., @commons/..., ...
-    // eslint-plugin-chai-friendly rules
-    'no-unused-expressions': 0,
-    'chai-friendly/no-unused-expressions': 2,
   },
 };

+ 7 - 1
CHANGES.md

@@ -4,7 +4,12 @@
 
 ### BREAKING CHANGES
 
-* GROWI no longer supports [Crowi Template syntax](https://medium.com/crowi-book/crowi-v1-5-0-5a62e7c6be90)
+* GROWI no longer supports
+    * Basic Authentication
+    * Crowi Classic Authentication Mechanism
+    * [Crowi Template syntax](https://medium.com/crowi-book/crowi-v1-5-0-5a62e7c6be90)
+
+Upgrading Guide: https://docs.growi.org/guide/upgrading/35x.html
 
 ### Updates
 
@@ -17,6 +22,7 @@
 * I18n: User Management Details
 * I18n: Group Management Details
 * Support: Apply unstated
+* Support: Apply Jest for Tests
 * Support: Upgrade libs
     * async
     * axios

+ 54 - 0
config/jest.config.js

@@ -0,0 +1,54 @@
+// For a detailed explanation regarding each configuration property, visit:
+// https://jestjs.io/docs/en/configuration.html
+
+module.exports = {
+  // Indicates whether each individual test should be reported during the run
+  verbose: true,
+
+  rootDir: '../',
+
+  // Automatically clear mock calls and instances between every test
+  clearMocks: true,
+  // Automatically reset mock state between every test
+  resetMocks: true,
+
+  projects: [
+    {
+      displayName: 'server',
+      testEnvironment: 'node',
+      rootDir: '.',
+      setupFilesAfterEnv: ['<rootDir>/src/test/setup.js'],
+      testMatch: ['<rootDir>/src/test/**/*.test.js'],
+      // A map from regular expressions to module names that allow to stub out resources with a single module
+      moduleNameMapper: {
+        '@root/(.+)': '<rootDir>/$1',
+        '@commons/(.+)': '<rootDir>/src/lib/$1',
+        '@server/(.+)': '<rootDir>/src/server/$1',
+        '@alias/logger': '<rootDir>/src/lib/service/logger',
+        // -- doesn't work with unknown error -- 2019.06.19 Yuki Takei
+        // debug: '<rootDir>/src/lib/service/logger/alias-for-debug',
+      },
+    },
+    // {
+    //   displayName: 'client',
+    //   rootDir: '.',
+    //   testMatch: ['<rootDir>/src/test/client/**/*.test.js'],
+    // },
+  ],
+
+
+  // Indicates whether the coverage information should be collected while executing the test
+  // collectCoverage: false,
+
+  // An array of glob patterns indicating a set of files for which coverage information should be collected
+  collectCoverageFrom: [
+    'src/client/**/*.js',
+    'src/lib/**/*.js',
+    'src/migrations/**/*.js',
+    'src/server/**/*.js',
+  ],
+
+  // The directory where Jest should output its coverage files
+  coverageDirectory: 'coverage',
+
+};

+ 1 - 0
config/logger/config.dev.js

@@ -15,6 +15,7 @@ module.exports = {
   // 'growi:routes:login': 'debug',
   'growi:routes:login-passport': 'debug',
   'growi:service:PassportService': 'debug',
+  // 'growi:service:ConfigManager': 'debug',
   'growi:lib:search': 'debug',
   // 'growi:service:GlobalNotification': 'debug',
   // 'growi:lib:importer': 'debug',

+ 3 - 0
config/migrate.js

@@ -5,6 +5,8 @@
  * @author Yuki Takei <yuki@weseek.co.jp>
  */
 
+require('module-alias/register');
+
 function getMongoUri(env) {
   return env.MONGOLAB_URI // for B.C.
     || env.MONGODB_URI // MONGOLAB changes their env name
@@ -15,6 +17,7 @@ function getMongoUri(env) {
 
 const mongoUri = getMongoUri(process.env);
 const match = mongoUri.match(/^(.+)\/([^/]+)$/);
+
 module.exports = {
   mongoUri,
   mongodb: {

+ 12 - 9
package.json

@@ -55,11 +55,15 @@
     "server:prod": "env-cmd -f config/env.prod.js node src/server/app.js",
     "server": "npm run server:dev",
     "start": "npm run server:prod",
-    "test": "mocha --timeout 10000 --exit -r src/test/bootstrap.js src/test/**/*.js",
+    "test": "jest --config=config/jest.config.js --passWithNoTests -- ",
     "version": "node -p \"require('./package.json').version\"",
     "webpack": "webpack"
   },
   "dependencies": {
+    "//": [
+      "check-node-version: see https://github.com/parshap/check-node-version/issues/35",
+      "mongoose: somehow GlobalNotificationSetting CRUD does not work with mongoose v5.6.0"
+    ],
     "async": "^3.0.1",
     "aws-sdk": "^2.88.0",
     "axios": "^0.19.0",
@@ -67,7 +71,6 @@
     "body-parser": "^1.18.2",
     "bunyan": "^1.8.12",
     "bunyan-format": "^0.2.1",
-    "//": "see https://github.com/parshap/check-node-version/issues/35",
     "check-node-version": "=3.3.0",
     "connect-flash": "~0.1.1",
     "connect-mongo": "^3.0.0",
@@ -88,7 +91,6 @@
     "express-session": "^1.16.1",
     "express-validator": "^5.3.1",
     "express-webpack-assets": "^0.1.0",
-    "googleapis": "^40.0.0",
     "graceful-fs": "^4.1.11",
     "growi-commons": "^4.0.1",
     "helmet": "^3.13.0",
@@ -101,7 +103,7 @@
     "migrate-mongo": "^6.0.0",
     "mkdirp": "~0.5.1",
     "module-alias": "^2.0.6",
-    "mongoose": "^5.6.0",
+    "mongoose": "5.4.4",
     "mongoose-gridfs": "^1.2.2",
     "mongoose-paginate": "^5.0.3",
     "mongoose-unique-validator": "^2.0.3",
@@ -148,7 +150,6 @@
     "browser-bunyan": "^1.3.0",
     "browser-sync": "^2.26.3",
     "bunyan-debug": "^2.0.0",
-    "chai": "^4.1.0",
     "cli": "~1.0.1",
     "codemirror": "^5.42.0",
     "colors": "^1.2.5",
@@ -161,13 +162,14 @@
     "eazy-logger": "^3.0.2",
     "eslint": "^5.15.1",
     "eslint-config-weseek": "^1.0.1",
-    "eslint-plugin-chai-friendly": "^0.4.1",
     "eslint-plugin-import": "^2.16.0",
+    "eslint-plugin-jest": "^22.6.4",
     "eslint-plugin-react": "^7.12.4",
     "file-loader": "^4.0.0",
     "handsontable": "^6.0.1",
     "i18next-browser-languagedetector": "^3.0.1",
     "imports-loader": "^0.8.0",
+    "jest": "^24.8.0",
     "jquery-slimscroll": "^1.3.8",
     "jquery-ui": "^1.12.1",
     "jquery.cookie": "~1.4.1",
@@ -185,7 +187,6 @@
     "markdown-table": "^1.1.1",
     "metismenu": "^3.0.3",
     "mini-css-extract-plugin": "^0.7.0",
-    "mocha": "^6.0.1",
     "morgan": "^1.9.0",
     "node-dev": "^4.0.0",
     "node-sass": "^4.11.0",
@@ -211,8 +212,6 @@
     "reveal.js": "^3.5.0",
     "sass-loader": "^7.1.0",
     "simple-load-script": "^1.0.2",
-    "sinon": "^7.2.2",
-    "sinon-chai": "^3.3.0",
     "socket.io-client": "^2.0.3",
     "style-loader": "^0.23.0",
     "stylelint-config-recess-order": "^2.0.1",
@@ -227,6 +226,10 @@
     "webpack-cli": "^3.2.3",
     "webpack-merge": "^4.2.1"
   },
+  "resolutions": {
+    "//": "see https://github.com/facebook/jest/issues/8155",
+    "babel-jest": "23.6.0"
+  },
   "_moduleAliases": {
     "@root": ".",
     "@commons": "src/lib",

+ 6 - 39
resource/locales/en-US/translation.json

@@ -81,9 +81,7 @@
   "Delete this image?": "Delete this image?",
   "Updated": "Updated",
   "Upload new image": "Upload new image",
-  "Google Setting": "Google Setting",
   "Connected": "Connected",
-  "Disconnect": "Disconnect",
   "Show": "Show",
   "Hide": "Hide",
   "Disclose E-mail": "Disclose E-mail",
@@ -124,7 +122,6 @@
   "Reselect the group": "Reselect the group",
   "Shareable link": "Shareable link",
   "The whitelist of registration permission E-mail address": "The whitelist of registration permission E-mail address",
-  "Selecting authentication mechanism": "Selecting authentication mechanism",
   "Add tags for this page": "Add tags for this page",
   "Edit tags for this page": "Edit tags for this page",
   "You have no tag, You can set tags on pages": "You have no tag, You can set tags on pages",
@@ -149,15 +146,13 @@
   },
 
   "breaking_changes": {
-    "v346_passport_is_not_enabled": "Crowi Classic Authentication mechanism currently in use will <strong>no longer be supported</strong> in the near future. Switch to Passport from %s",
     "v346_using_basic_auth": "Basic Authentication currently in use will <strong>no longer be available</strong> in the near future. Remove settings from %s"
   },
 
   "page_register": {
     "notice": {
       "restricted": "Admin approval required.",
-      "restricted_defail": "Once the admin approves your sign up, you'll be able to access this wiki.",
-      "google_account_continue": "Enter your user ID, name and password to continue."
+      "restricted_defail": "Once the admin approves your sign up, you'll be able to access this wiki."
     },
     "form_help": {
       "email": "You must have email address which listed below to sign up to this wiki.",
@@ -169,11 +164,7 @@
   "page_me": {
     "form_help": {
       "profile_image1": "Image upload settings not completed.",
-      "profile_image2": "Set up AWS or enable local uploads.",
-      "google_connect1": "With Google Connect, you can sign in with your Google Account.",
-      "google_connect2": "Only Google Apps accounts with the following email addresses are connectable Google accounts:",
-      "google_disconnect1": "If you disconnect your Google account, you will be unable to sign in using Google Authentication",
-      "google_disconnect2": "After disconnecting your Google account, you can sign in normally using your email and password"
+      "profile_image2": "Set up AWS or enable local uploads."
     }
   },
   "page_me_apitoken": {
@@ -444,7 +435,6 @@
 		"Guest users access": "Guest users access",
 		"Register limitation": "Register limitation",
 		"The whitelist of registration permission E-mail address": "The whitelist of registration permission E-mail address",
-		"Selecting authentication mechanism": "Selecting authentication mechanism",
 		"common_authentication": "If you set the basic authentication, common authentication is applied on the whole page.",
 		"without_encryption": "Please be noted that your ID and Password will be sent wihtout encryption.",
 		"basic_acl_disable": "Because of Public Wiki  setting, basic authentication can not be used.",
@@ -464,33 +454,8 @@
     "admin_and_author": "Admin and Author",
     "anyone": "Anyone",
 
-		"Authentication mechanism settings": "Authentication mechanism settings",
-    "note": "Note",
-    "require_server_restart_change_auth": "Restarting the server is required if you switch the auth mechanism.",
-    "auth_mechanism": "authentication mechanism",
-    "recommended": "Recommended",
-    "username_email_password": "Username, Email and Password authentication",
+		"Authentication mechanism settings": "Authentication Mechanism Settings",
     "alert_siteUrl_is_not_set": "'Site URL' is NOT set. Set it from the %s",
-    "ldap_auth": "LDAP authentication",
-    "saml_auth": "SAML authentication",
-    "google_auth2": "Google OAuth authentication",
-    "google_auth2_by_crowi_desc": "However, this feature does not create new users, butit only makes it possible to login to the existing user who set up the association.",
-    "facebook_auth2": "Facebook OAuth authentication",
-    "twitter_auth2": "Twitter OAuth authentication",
-    "github_auth2": "GitHub OAuth authentication",
-    "crowi_auth": "Crowi classic authentication mechanism",
-		"require_server_restart": "Restarting the server is required.",
-		"server_on_passport_auth": "The server is running with Passport authentication mechanism.",
-		"server_on_crowi_auth": "The server is running with official Crowi authentication mechanism.",
-		"google_setting": "Google Setting",
-    "connect_api_manager": "You can use your Google account to sign up and login after creating OAuth2 ClientId at <a href=\"https://console.cloud.google.com/apis/credentials\" target=\"_blank\">API Manager on Google Cloud Platform</a>",
-		"access_api_manager": "Access <a href=\"%s\" target=\"_blank\">%s</a>",
-		"create_project": "Create Project if no projects have been created.",
-		"create_auth_to_oauth": "\"Create credentials\" -> \"OAuth clientID\"",
-		"select_webapp": "Select \"Web Application\"",
-    "change_redirect_url": "Enter <code>https://${crowi.host}/google/callback</code> <br>(where <code>${crowi.host}</code> is your host name) for \"Authorized redirect URIs\".",
-    "clientID": "Client ID",
-    "client_secret": "Client Secret",
     "xss_prevent_setting":"Prevent XSS(Cross Site Scripting)",
     "xss_prevent_setting_link":"Go to Markdown settings",
     "callback_URL": "Callback URL",
@@ -498,13 +463,15 @@
     "issuerHost": "Issuer Host",
     "scope": "Scope",
     "desc_of_callback_URL": "Use it in the setting of the %s provider",
+    "clientID": "Client ID",
+    "client_secret": "Client Secret",
     "guest_mode": {
       "deny": "Deny Unregistered Users",
       "readonly": "View Only"
     },
     "registration_mode": {
       "open": "Anyone",
-      "restricted": "Reuqire Admin permission",
+      "restricted": "Require Admin permission",
       "closed": "Invitation Only"
     },
     "configuration": " Configuration",

+ 4 - 37
resource/locales/ja/translation.json

@@ -81,9 +81,7 @@
   "Delete this image?": "削除してよろしいですか?",
   "Updated": "更新しました",
   "Upload new image": "新しい画像をアップロード",
-  "Google Setting": "Google設定",
   "Connected": "接続されています",
-  "Disconnect": "接続を解除",
   "Show": "公開",
   "Hide": "非公開",
   "Disclose E-mail": "メールアドレスの公開",
@@ -124,7 +122,6 @@
   "Reselect the group": "グループの再選択",
   "Shareable link": "このページの共有用URL",
   "The whitelist of registration permission E-mail address": "登録許可メールアドレスの<br>ホワイトリスト",
-  "Selecting authentication mechanism": "認証機構選択",
   "Add tags for this page": "タグを付ける",
   "Edit tags for this page": "タグを編集する",
   "You have no tag, You can set tags on pages": "使用中のタグがありません",
@@ -149,15 +146,13 @@
   },
 
   "breaking_changes": {
-    "v346_passport_is_not_enabled": "現在利用中の Crowi Classic Authentication mechanism は、近い将来<strong>サポートされなくなります</strong>。%s から Passport に切り替えてください。",
     "v346_using_basic_auth": "現在利用中の Basic 認証機能は、近い将来<strong>廃止されます</strong>。%s から設定を削除してください。"
   },
 
   "page_register": {
     "notice": {
        "restricted": "この Wiki への新規登録は制限されています。",
-       "restricted_defail": "利用を開始するには、新規登録後、管理者による承認が必要です。",
-       "google_account_continue": "ユーザーID、名前、パスワードを決めて登録を継続してください。"
+       "restricted_defail": "利用を開始するには、新規登録後、管理者による承認が必要です。"
     },
     "form_help": {
       "email": "この Wiki では以下のメールアドレスのみ登録可能です。",
@@ -169,11 +164,7 @@
   "page_me": {
     "form_help": {
       "profile_image1": "画像をアップロードをするための設定がされていません。",
-      "profile_image2": "アップロードできるようにするには、AWS またはローカルアップロードの設定をしてください。",
-      "google_connect1": "Googleコネクトをすると、Googleアカウントでログイン可能になります。",
-      "google_connect2": "コネクト可能なGoogleアカウントは、以下のメールアドレスの発行できるGoogle Appsアカウントに限られます。",
-      "google_disconnect1": "接続を解除すると、Googleでログインができなくなります。",
-      "google_disconnect2": "解除後はメールアドレスとパスワードでログインすることができます。"
+      "profile_image2": "アップロードできるようにするには、AWS またはローカルアップロードの設定をしてください。"
     }
   },
   "page_me_apitoken": {
@@ -443,7 +434,6 @@
     "Guest users access": "ゲストユーザーのアクセス",
     "Register limitation": "登録の制限",
     "The whitelist of registration permission E-mail address": "登録許可メールアドレスの<br>ホワイトリスト",
-    "Selecting authentication mechanism": "認証機構選択",
     "common_authentication": "Basic認証を設定すると、ページ全体に共通の認証がかかります。",
     "without_encryption": "IDとパスワードは暗号化されずに送信されるのでご注意下さい。",
     "basic_acl_disable": "Public Wiki の設定のため、Basic認証は利用できません。",
@@ -464,36 +454,13 @@
     "anyone": "誰でも可能",
 
     "Authentication mechanism settings":"認証機構設定",
-    "note": "メモ",
-    "require_server_restart_change_auth": "認証機構の変更後はサーバーを再起動してください。",
-    "auth_mechanism": "認証機構",
-    "recommended": "推奨",
-    "username_email_password": "ユーザー名、Eメール、パスワードでの認証",
     "alert_siteUrl_is_not_set": "'サイトURL' が設定されていません。%s から設定してください。",
-    "ldap_auth": "LDAP 認証",
-    "saml_auth": "SAML 認証",
-    "google_auth2": "Google OAuth 認証",
-    "google_auth2_by_crowi_desc": "ただし、この機能では新たなユーザーは作成されず、関連付け設定を行った既存ユーザーをログインできるようにするだけです。",
-    "facebook_auth2": "Facebook OAuth 認証",
-    "twitter_auth2": "Twitter OAuth 認証",
-    "github_auth2": "GitHub OAuth 認証",
-    "crowi_auth": "Crowi Classic OAuth 認証",
-    "require_server_restart": "サーバーを再起動してください。",
-    "server_on_passport_auth": "Passport 認証機構でサーバーが稼働しています。",
-    "server_on_crowi_auth": "Crowi Classic 認証機構でサーバーが稼働しています。",
-    "google_setting": "Google 設定",
-    "connect_api_manager": "Google Cloud Platform の <a href=\"https://console.cloud.google.com/apis/credentials\" target=\"_blank\">API Manager</a>から OAuth2 Client ID を作成すると、Google アカウントにコネクトして登録やログインが可能になります。",
-    "access_api_manager": "<a href=\"%s\" target=\"_blank\">%s</a> へアクセス",
-    "create_project": "プロジェクトを作成していない場合は作成してください",
-    "create_auth_to_oauth": "「認証情報を作成」-> OAuthクライアントID",
-    "select_webapp": "「ウェブアプリケーション」を選択",
-    "change_redirect_url": "承認済みのリダイレクトURLに、 <code>https://${crowi.host}/google/callback</code> を入力<br>(<code>${crowi.host}</code>は環境に合わせて変更してください)",
-    "clientID": "クライアントID",
-    "client_secret": "クライアントシークレット",
     "xss_prevent_setting":"XSS(Cross Site Scripting)対策設定",
     "xss_prevent_setting_link":"マークダウン設定ページに移動",
     "callback_URL": "コールバックURL",
     "desc_of_callback_URL": "%s プロバイダ側の設定で利用してください。",
+    "clientID": "クライアントID",
+    "client_secret": "クライアントシークレット",
     "guest_mode": {
       "deny": "アカウントを持たないユーザーはアクセス不可",
       "readonly": "閲覧のみ許可"

+ 1 - 1
src/client/js/app.js

@@ -121,7 +121,7 @@ if (pageContainer.state.path != null) {
   componentMappings = Object.assign({
     // eslint-disable-next-line quote-props
     'page': <Page />,
-    'revision-path':  <RevisionPath pageId={pageContainer.state.pageId} pagePath={pageContainer.state.path} crowi={appContainer} />,
+    'revision-path': <RevisionPath behaviorType={appContainer.config.behaviorType} pageId={pageContainer.state.pageId} pagePath={pageContainer.state.path} />,
     'tag-label':  <TagLabels />,
   }, componentMappings);
 }

+ 3 - 3
src/client/js/components/Page/RevisionPath.jsx

@@ -26,8 +26,8 @@ class RevisionPath extends React.Component {
     this.setState({ isListPage });
 
     // whether set link to '/'
-    const behaviorType = this.props.crowi.getConfig().behaviorType;
-    const isLinkToListPage = (!behaviorType || behaviorType === 'crowi');
+    const { behaviorType } = this.props;
+    const isLinkToListPage = (behaviorType === 'crowi');
     this.setState({ isLinkToListPage });
 
     // generate pages obj
@@ -127,7 +127,7 @@ class RevisionPath extends React.Component {
 
 RevisionPath.propTypes = {
   t: PropTypes.func.isRequired, // i18next
-  crowi: PropTypes.object.isRequired,
+  behaviorType: PropTypes.string.isRequired,
   pagePath: PropTypes.string.isRequired,
   pageId: PropTypes.string,
 };

+ 12 - 0
src/lib/util/mongoose-utils.js

@@ -0,0 +1,12 @@
+const mongoose = require('mongoose');
+
+const getModelSafely = (modelName) => {
+  if (mongoose.modelNames().includes(modelName)) {
+    return mongoose.model(modelName);
+  }
+  return null;
+};
+
+module.exports = {
+  getModelSafely,
+};

+ 3 - 2
src/migrations/20180926134048-make-email-unique.js

@@ -1,16 +1,17 @@
-require('module-alias/register');
 const logger = require('@alias/logger')('growi:migrate:make-email-unique');
 
 const mongoose = require('mongoose');
 const config = require('@root/config/migrate');
 
+const { getModelSafely } = require('@commons/util/mongoose-utils');
+
 module.exports = {
 
   async up(db, next) {
     logger.info('Start migration');
     mongoose.connect(config.mongoUri, config.mongodb.options);
 
-    const User = require('@server/models/user')();
+    const User = getModelSafely('User') || require('@server/models/user')();
 
     // get all users who has 'deleted@deleted' email
     const users = await User.find({ email: 'deleted@deleted' });

+ 13 - 6
src/migrations/20180927102719-init-serverurl.js

@@ -1,11 +1,12 @@
 
 
-require('module-alias/register');
 const logger = require('@alias/logger')('growi:migrate:init-serverurl');
 
 const mongoose = require('mongoose');
 const config = require('@root/config/migrate');
 
+const { getModelSafely } = require('@commons/util/mongoose-utils');
+
 /**
  * check all values of the array are equal
  * @see https://stackoverflow.com/a/35568895
@@ -22,7 +23,7 @@ module.exports = {
     logger.info('Apply migration');
     mongoose.connect(config.mongoUri, config.mongodb.options);
 
-    const Config = require('@server/models/config')();
+    const Config = getModelSafely('Config') || require('@server/models/config')();
 
     // find 'app:siteUrl'
     const siteUrlConfig = await Config.findOne({
@@ -66,16 +67,22 @@ module.exports = {
     }
 
     if (siteUrl != null) {
-      await Config.findOneAndUpdateByNsAndKey('crowi', 'app:siteUrl', siteUrl);
+      const ns = 'crowi';
+      const key = 'app:siteUrl';
+      await Config.findOneAndUpdate(
+        { ns, key },
+        { ns, key, value: JSON.stringify(siteUrl) },
+        { upsert: true },
+      );
       logger.info('Migration has successfully applied');
     }
   },
 
   async down(db) {
-    logger.info('Undo migration');
+    logger.info('Rollback migration');
     mongoose.connect(config.mongoUri, config.mongodb.options);
 
-    const Config = require('@server/models/config')();
+    const Config = getModelSafely('Config') || require('@server/models/config')();
 
     // remote 'app:siteUrl'
     await Config.findOneAndDelete({
@@ -83,7 +90,7 @@ module.exports = {
       key: 'app:siteUrl',
     });
 
-    logger.info('Migration has successfully undoed');
+    logger.info('Migration has been successfully rollbacked');
   },
 
 };

+ 11 - 8
src/migrations/20181019114028-abolish-page-group-relation.js

@@ -1,9 +1,10 @@
-require('module-alias/register');
 const logger = require('@alias/logger')('growi:migrate:abolish-page-group-relation');
 
 const mongoose = require('mongoose');
 const config = require('@root/config/migrate');
 
+const { getModelSafely } = require('@commons/util/mongoose-utils');
+
 
 async function isCollectionExists(db, collectionName) {
   const collections = await db.listCollections({ name: collectionName }).toArray();
@@ -37,8 +38,8 @@ module.exports = {
       return;
     }
 
-    const Page = require('@server/models/page')();
-    const UserGroup = require('@server/models/user-group')();
+    const Page = getModelSafely('Page') || require('@server/models/page')();
+    const UserGroup = getModelSafely('UserGroup') || require('@server/models/user-group')();
 
     // retrieve all documents from 'pagegrouprelations'
     const relations = await db.collection('pagegrouprelations').find().toArray();
@@ -71,11 +72,11 @@ module.exports = {
   },
 
   async down(db) {
-    logger.info('Undo migration');
+    logger.info('Rollback migration');
     mongoose.connect(config.mongoUri, config.mongodb.options);
 
-    const Page = require('@server/models/page')();
-    const UserGroup = require('@server/models/user-group')();
+    const Page = getModelSafely('Page') || require('@server/models/page')();
+    const UserGroup = getModelSafely('UserGroup') || require('@server/models/user-group')();
 
     // retrieve all Page documents which granted by UserGroup
     const relatedPages = await Page.find({ grant: Page.GRANT_USER_GROUP });
@@ -106,9 +107,11 @@ module.exports = {
     }
     /* eslint-enable no-await-in-loop */
 
-    await db.collection('pagegrouprelations').insertMany(insertDocs);
+    if (insertDocs.length > 0) {
+      await db.collection('pagegrouprelations').insertMany(insertDocs);
+    }
 
-    logger.info('Migration has successfully undoed');
+    logger.info('Migration has been successfully rollbacked');
   },
 
 };

+ 43 - 0
src/migrations/20190618055300-abolish-crowi-classic-auth.js

@@ -0,0 +1,43 @@
+const logger = require('@alias/logger')('growi:migrate:abolish-crowi-classic-auth');
+
+const mongoose = require('mongoose');
+const config = require('@root/config/migrate');
+
+const { getModelSafely } = require('@commons/util/mongoose-utils');
+
+
+module.exports = {
+  async up(db, next) {
+    logger.info('Start migration');
+    mongoose.connect(config.mongoUri, config.mongodb.options);
+
+    const Config = getModelSafely('Config') || require('@server/models/config')();
+
+    // enable passport and delete configs for crowi classic auth
+    await Promise.all([
+      Config.findOneAndUpdate(
+        { ns: 'crowi', key: 'security:isEnabledPassport' },
+        { ns: 'crowi', key: 'security:isEnabledPassport', value: JSON.stringify(true) },
+        { upsert: true },
+      ),
+      Config.findOneAndUpdate(
+        { ns: 'crowi', key: 'google:clientId' },
+        { ns: 'crowi', key: 'google:clientId', value: JSON.stringify(null) },
+        { upsert: true },
+      ),
+      Config.findOneAndUpdate(
+        { ns: 'crowi', key: 'google:clientSecret' },
+        { ns: 'crowi', key: 'google:clientSecret', value: JSON.stringify(null) },
+        { upsert: true },
+      ),
+    ]);
+
+    logger.info('Migration has successfully terminated');
+    next();
+  },
+
+  async down(db, next) {
+    // do not rollback
+    next();
+  },
+};

+ 64 - 0
src/migrations/20190618104011-add-config-app-installed.js

@@ -0,0 +1,64 @@
+const logger = require('@alias/logger')('growi:migrate:add-config-app-installed');
+
+const mongoose = require('mongoose');
+const config = require('@root/config/migrate');
+
+const { getModelSafely } = require('@commons/util/mongoose-utils');
+
+
+/**
+ * BEFORE
+ *   - Config document { ns: 'crowi', key: 'app:installed' } does not exist
+ * AFTER
+ *   - Config document { ns: 'crowi', key: 'app:installed' } is created
+ *     - value will be true if one or more users exist
+ *     - value will be false if no users exist
+ */
+module.exports = {
+
+  async up(db) {
+    logger.info('Apply migration');
+    mongoose.connect(config.mongoUri, config.mongodb.options);
+
+    const Config = getModelSafely('Config') || require('@server/models/config')();
+    const User = getModelSafely('User') || require('@server/models/user')();
+
+    // find 'app:siteUrl'
+    const appInstalled = await Config.findOne({
+      ns: 'crowi',
+      key: 'app:installed',
+    });
+    // exit if exists
+    if (appInstalled != null) {
+      logger.info('\'app:appInstalled\' is already exists. This migration terminates without any changes.');
+      return;
+    }
+
+    const userCount = await User.count();
+
+    if (userCount > 0) {
+      await Config.create({
+        ns: 'crowi',
+        key: 'app:installed',
+        value: true,
+      });
+    }
+
+    logger.info('Migration has successfully applied');
+  },
+
+  async down(db) {
+    logger.info('Rollback migration');
+    mongoose.connect(config.mongoUri, config.mongodb.options);
+
+    const Config = getModelSafely('Config') || require('@server/models/config')();
+
+    // remote 'app:siteUrl'
+    await Config.findOneAndDelete({
+      ns: 'crowi',
+      key: 'app:installed',
+    });
+
+    logger.info('Migration has been successfully rollbacked');
+  },
+};

+ 15 - 50
src/server/crowi/express-init.js

@@ -11,7 +11,6 @@ module.exports = function(crowi, app) {
   const passport = require('passport');
   const expressSession = require('express-session');
   const sanitizer = require('express-sanitizer');
-  const basicAuth = require('basic-auth-connect');
   const flash = require('connect-flash');
   const swig = require('swig-templates');
   const webpackAssets = require('express-webpack-assets');
@@ -22,15 +21,12 @@ module.exports = function(crowi, app) {
 
   const avoidSessionRoutes = require('../routes/avoid-session-routes');
   const i18nUserSettingDetector = require('../util/i18nUserSettingDetector');
-  const middleware = require('../util/middlewares');
 
   const env = crowi.node_env;
 
-  // Old type config API
-  const config = crowi.getConfig();
-  const Config = crowi.model('Config');
   // New type config API
   const configManager = crowi.configManager;
+  const getConfig = configManager.getConfig;
 
   const User = crowi.model('User');
   const lngDetector = new i18nMiddleware.LanguageDetector();
@@ -60,7 +56,7 @@ module.exports = function(crowi, app) {
 
   app.use((req, res, next) => {
     const now = new Date();
-    const tzoffset = -(config.crowi['app:timezone'] || 9) * 60;
+    const tzoffset = -(getConfig('crowi', 'app:timezone') || 9) * 60;
     // for datez
 
     const Page = crowi.model('Page');
@@ -68,12 +64,10 @@ module.exports = function(crowi, app) {
     const Config = crowi.model('Config');
     app.set('tzoffset', tzoffset);
 
-    req.config = config;
     req.csrfToken = null;
 
     res.locals.req = req;
-    res.locals.baseUrl = configManager.getSiteUrl();
-    res.locals.config = config;
+    res.locals.baseUrl = crowi.appService.getSiteUrl();
     res.locals.env = env;
     res.locals.now = now;
     res.locals.tzoffset = tzoffset;
@@ -81,10 +75,10 @@ module.exports = function(crowi, app) {
       pageGrants: Page.getGrantLabels(),
       userStatus: User.getUserStatusLabels(),
       language:   User.getLanguageLabels(),
-      restrictGuestMode: Config.getRestrictGuestModeLabels(),
-      registrationMode: Config.getRegistrationModeLabels(),
+      restrictGuestMode: crowi.aclService.getRestrictGuestModeLabels(),
+      registrationMode: crowi.aclService.getRegistrationModeLabels(),
     };
-    res.locals.local_config = Config.getLocalconfig(config); // config for browser context
+    res.locals.local_config = Config.getLocalconfig(); // config for browser context
 
     next();
   });
@@ -118,50 +112,21 @@ module.exports = function(crowi, app) {
     expressSession(crowi.sessionConfig)(req, res, next);
   });
 
-  // Set basic auth middleware
-  app.use((req, res, next) => {
-    if (req.query.access_token || req.body.access_token) {
-      return next();
-    }
-
-    // FIXME:
-    //   healthcheck endpoint exclude from basic authentication.
-    //   however, hard coding is not desirable.
-    //   need refactoring (ex. setting basic authentication for each routes)
-    if (req.path === '/_api/v3/healthcheck') {
-      return next();
-    }
-
-    const basicName = configManager.getConfig('crowi', 'security:basicName');
-    const basicSecret = configManager.getConfig('crowi', 'security:basicSecret');
-    if (basicName && basicSecret) {
-      return basicAuth(basicName, basicSecret)(req, res, next);
-    }
-
-    next();
-  });
-
   // passport
-  if (Config.isEnabledPassport(config)) {
-    debug('initialize Passport');
-    app.use(passport.initialize());
-    app.use(passport.session());
-  }
+  debug('initialize Passport');
+  app.use(passport.initialize());
+  app.use(passport.session());
 
   app.use(flash());
 
-  app.use(middleware.swigFilters(crowi, app, swig));
-  app.use(middleware.swigFunctions(crowi, app));
+  const middlewares = require('../util/middlewares')(crowi, app);
+
+  app.use(middlewares.swigFilters(swig));
+  app.use(middlewares.swigFunctions());
 
-  app.use(middleware.csrfKeyGenerator(crowi, app));
+  app.use(middlewares.csrfKeyGenerator());
 
-  // switch loginChecker
-  if (Config.isEnabledPassport(config)) {
-    app.use(middleware.loginCheckerForPassport(crowi, app));
-  }
-  else {
-    app.use(middleware.loginChecker(crowi, app));
-  }
+  app.use(middlewares.loginCheckerForPassport);
 
   app.use(i18nMiddleware.handle(i18next));
 };

+ 129 - 46
src/server/crowi/index.js

@@ -38,6 +38,11 @@ function Crowi(rootdir) {
   this.mailer = {};
   this.passportService = null;
   this.globalNotificationService = null;
+  this.slackNotificationService = null;
+  this.xssService = null;
+  this.aclService = null;
+  this.appService = null;
+  this.fileUploadService = null;
   this.restQiitaAPIService = null;
   this.cdnResourcesService = new CdnResourcesService();
   this.interceptorManager = new InterceptorManager();
@@ -73,9 +78,17 @@ Crowi.prototype.init = async function() {
   await this.setupDatabase();
   await this.setupModels();
   await this.setupSessionConfig();
-  await this.setupAppConfig();
   await this.setupConfigManager();
 
+  // customizeService depends on AppService and XssService
+  // passportService depends on appService
+  // slack depends on setUpSlacklNotification
+  await Promise.all([
+    this.setUpApp(),
+    this.setUpXss(),
+    this.setUpSlacklNotification(),
+  ]);
+
   await Promise.all([
     this.scanRuntimeVersions(),
     this.setupPassport(),
@@ -84,7 +97,39 @@ Crowi.prototype.init = async function() {
     this.setupSlack(),
     this.setupCsrf(),
     this.setUpGlobalNotification(),
+    this.setUpFileUpload(),
+    this.setUpAcl(),
+    this.setUpCustomize(),
     this.setUpRestQiitaAPI(),
+    this.setupUserGroup(),
+  ]);
+};
+
+Crowi.prototype.initForTest = async function() {
+  await this.setupModels();
+  await this.setupConfigManager();
+
+  // // customizeService depends on AppService and XssService
+  // // passportService depends on appService
+  // // slack depends on setUpSlacklNotification
+  await Promise.all([
+    this.setUpApp(),
+    // this.setUpXss(),
+    // this.setUpSlacklNotification(),
+  ]);
+
+  await Promise.all([
+  //   this.scanRuntimeVersions(),
+  //   this.setupPassport(),
+  //   this.setupSearcher(),
+  //   this.setupMailer(),
+  //   this.setupSlack(),
+  //   this.setupCsrf(),
+  //   this.setUpGlobalNotification(),
+  //   this.setUpFileUpload(),
+    this.setUpAcl(),
+  //   this.setUpCustomize(),
+  //   this.setUpRestQiitaAPI(),
   ]);
 };
 
@@ -180,43 +225,23 @@ Crowi.prototype.setupSessionConfig = function() {
   }));
 };
 
-Crowi.prototype.setupAppConfig = function() {
-  return new Promise((resolve, reject) => {
-    this.model('Config', require('../models/config')(this));
-    const Config = this.model('Config');
-    Config.loadAllConfig((err, doc) => {
-      if (err) {
-        return reject();
-      }
-
-      this.setConfig(doc);
-
-      return resolve();
-    });
-  });
-};
-
 Crowi.prototype.setupConfigManager = async function() {
   const ConfigManager = require('../service/config-manager');
   this.configManager = new ConfigManager(this.model('Config'));
   return this.configManager.loadConfigs();
 };
 
-Crowi.prototype.setupModels = function() {
-  const self = this;
-  return new Promise(((resolve, reject) => {
-    Object.keys(models).forEach((key) => {
-      self.model(key, models[key](self));
-    });
-    resolve();
-  }));
+Crowi.prototype.setupModels = async function() {
+  Object.keys(models).forEach((key) => {
+    return this.model(key, models[key](this));
+  });
 };
 
 Crowi.prototype.getIo = function() {
   return this.io;
 };
 
-Crowi.prototype.scanRuntimeVersions = function() {
+Crowi.prototype.scanRuntimeVersions = async function() {
   const self = this;
 
   const check = require('check-node-version');
@@ -251,15 +276,7 @@ Crowi.prototype.getRestQiitaAPIService = function() {
   return this.restQiitaAPIService;
 };
 
-Crowi.prototype.setupPassport = function() {
-  const config = this.getConfig();
-  const Config = this.model('Config');
-
-  if (!Config.isEnabledPassport(config)) {
-    // disabled
-    return;
-  }
-
+Crowi.prototype.setupPassport = async function() {
   debug('Passport is enabled');
 
   // initialize service
@@ -285,7 +302,7 @@ Crowi.prototype.setupPassport = function() {
   return Promise.resolve();
 };
 
-Crowi.prototype.setupSearcher = function() {
+Crowi.prototype.setupSearcher = async function() {
   const self = this;
   const searcherUri = this.env.ELASTICSEARCH_URI
     || this.env.BONSAI_URL
@@ -304,7 +321,7 @@ Crowi.prototype.setupSearcher = function() {
   }));
 };
 
-Crowi.prototype.setupMailer = function() {
+Crowi.prototype.setupMailer = async function() {
   const self = this;
   return new Promise(((resolve, reject) => {
     self.mailer = require('../util/mailer')(self);
@@ -312,13 +329,11 @@ Crowi.prototype.setupMailer = function() {
   }));
 };
 
-Crowi.prototype.setupSlack = function() {
+Crowi.prototype.setupSlack = async function() {
   const self = this;
-  const config = this.getConfig();
-  const Config = this.model('Config');
 
   return new Promise(((resolve, reject) => {
-    if (Config.hasSlackConfig(config)) {
+    if (this.slackNotificationService.hasSlackConfig()) {
       self.slack = require('../util/slack')(self);
     }
 
@@ -326,7 +341,7 @@ Crowi.prototype.setupSlack = function() {
   }));
 };
 
-Crowi.prototype.setupCsrf = function() {
+Crowi.prototype.setupCsrf = async function() {
   const Tokens = require('csrf');
   this.tokens = new Tokens();
 
@@ -377,8 +392,7 @@ Crowi.prototype.buildServer = function() {
   require('./express-init')(this, express);
 
   // import plugins
-  const Config = this.model('Config');
-  const isEnabledPlugins = Config.isEnabledPlugins(this.config);
+  const isEnabledPlugins = this.configManager.getConfig('crowi', 'plugin:isEnabledPlugins');
   if (isEnabledPlugins) {
     debug('Plugins are enabled');
     const PluginService = require('../plugins/plugin.service');
@@ -431,21 +445,90 @@ Crowi.prototype.require = function(modulePath) {
 /**
  * setup GlobalNotificationService
  */
-Crowi.prototype.setUpGlobalNotification = function() {
+Crowi.prototype.setUpGlobalNotification = async function() {
   const GlobalNotificationService = require('../service/global-notification');
   if (this.globalNotificationService == null) {
     this.globalNotificationService = new GlobalNotificationService(this);
   }
 };
 
+/**
+ * setup SlackNotificationService
+ */
+Crowi.prototype.setUpSlacklNotification = async function() {
+  const SlackNotificationService = require('../service/slack-notification');
+  if (this.slackNotificationService == null) {
+    this.slackNotificationService = new SlackNotificationService(this.configManager);
+  }
+};
+
+/**
+ * setup XssService
+ */
+Crowi.prototype.setUpXss = async function() {
+  const XssService = require('../service/xss');
+  if (this.xssService == null) {
+    this.xssService = new XssService(this.configManager);
+  }
+};
+
+/**
+ * setup AclService
+ */
+Crowi.prototype.setUpAcl = async function() {
+  const AclService = require('../service/acl');
+  if (this.aclService == null) {
+    this.aclService = new AclService(this.configManager);
+  }
+};
+
+/**
+ * setup CustomizeService
+ */
+Crowi.prototype.setUpCustomize = async function() {
+  const CustomizeService = require('../service/customize');
+  if (this.customizeService == null) {
+    this.customizeService = new CustomizeService(this.configManager, this.appService, this.xssService);
+    this.customizeService.initCustomCss();
+    this.customizeService.initCustomTitle();
+  }
+};
+
+/**
+ * setup AppService
+ */
+Crowi.prototype.setUpApp = async function() {
+  const AppService = require('../service/app');
+  if (this.appService == null) {
+    this.appService = new AppService(this.configManager);
+  }
+};
+
+/**
+ * setup FileUploadService
+ */
+Crowi.prototype.setUpFileUpload = async function() {
+  if (this.fileUploadService == null) {
+    this.fileUploadService = require('../service/file-uploader')(this);
+  }
+};
+
 /**
  * setup RestQiitaAPIService
  */
-Crowi.prototype.setUpRestQiitaAPI = function() {
+Crowi.prototype.setUpRestQiitaAPI = async function() {
   const RestQiitaAPIService = require('../service/rest-qiita-API');
   if (this.restQiitaAPIService == null) {
     this.restQiitaAPIService = new RestQiitaAPIService(this);
   }
 };
 
+Crowi.prototype.setupUserGroup = async function() {
+  const UserGroupService = require('../service/user-group');
+  if (this.userGroupService == null) {
+    this.userGroupService = new UserGroupService(this);
+    return this.userGroupService.init();
+  }
+};
+
 module.exports = Crowi;

+ 0 - 2
src/server/form/admin/securityGeneral.js

@@ -5,8 +5,6 @@ const stringToArray = require('../../util/formUtil').stringToArrayFilter;
 const normalizeCRLF = require('../../util/formUtil').normalizeCRLFFilter;
 
 module.exports = form(
-  field('settingForm[security:basicName]'),
-  field('settingForm[security:basicSecret]'),
   field('settingForm[security:restrictGuestMode]').required(),
   field('settingForm[security:registrationMode]').required(),
   field('settingForm[security:registrationWhiteList]').custom(normalizeCRLF).custom(stringToArray),

+ 0 - 8
src/server/form/admin/securityGoogle.js

@@ -1,8 +0,0 @@
-const form = require('express-form');
-
-const field = form.field;
-
-module.exports = form(
-  field('settingForm[google:clientId]').trim().is(/^[\da-z\-.]+$/),
-  field('settingForm[google:clientSecret]').trim().is(/^[\da-zA-Z\-_]+$/),
-);

+ 0 - 7
src/server/form/admin/securityMechanism.js

@@ -1,7 +0,0 @@
-const form = require('express-form');
-
-const field = form.field;
-
-module.exports = form(
-  field('settingForm[security:isEnabledPassport]').trim().toBooleanStrict(),
-);

+ 0 - 2
src/server/form/index.js

@@ -19,8 +19,6 @@ module.exports = {
     importerQiita: require('./admin/importerQiita'),
     plugin: require('./admin/plugin'),
     securityGeneral: require('./admin/securityGeneral'),
-    securityGoogle: require('./admin/securityGoogle'),
-    securityMechanism: require('./admin/securityMechanism'),
     securityPassportLdap: require('./admin/securityPassportLdap'),
     securityPassportSaml: require('./admin/securityPassportSaml'),
     securityPassportBasic: require('./admin/securityPassportBasic'),

+ 0 - 2
src/server/form/register.js

@@ -7,7 +7,5 @@ module.exports = form(
   field('registerForm.name').required(),
   field('registerForm.email').required(),
   field('registerForm.password').required().is(/^[\x20-\x7F]{6,}$/),
-  field('registerForm.googleId'),
-  field('registerForm.googleImage'),
   field('registerForm[app:globalLang]'),
 );

+ 2 - 2
src/server/models/attachment.js

@@ -11,8 +11,6 @@ const mongoose = require('mongoose');
 const ObjectId = mongoose.Schema.Types.ObjectId;
 
 module.exports = function(crowi) {
-  const fileUploader = require('../service/file-uploader')(crowi);
-
   function generateFileHash(fileName) {
     const hash = require('crypto').createHash('md5');
     hash.update(`${fileName}_${Date.now()}`);
@@ -44,6 +42,7 @@ module.exports = function(crowi) {
 
 
   attachmentSchema.statics.create = async function(pageId, user, fileStream, originalName, fileFormat, fileSize) {
+    const fileUploader = require('../service/file-uploader')(crowi);
     const Attachment = this;
 
     const extname = path.extname(originalName);
@@ -94,6 +93,7 @@ module.exports = function(crowi) {
   };
 
   attachmentSchema.statics.removeWithSubstanceById = async function(id) {
+    const fileUploader = require('../service/file-uploader')(crowi);
     // retrieve data from DB to get a completely populated instance
     const attachment = await this.findById(id);
     await fileUploader.deleteFile(attachment);

+ 58 - 545
src/server/models/config.js

@@ -5,17 +5,6 @@
 
 module.exports = function(crowi) {
   const mongoose = require('mongoose');
-  const debug = require('debug')('growi:models:config');
-  const uglifycss = require('uglifycss');
-  const recommendedWhitelist = require('@commons/service/xss/recommended-whitelist');
-
-  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';
-
-  let Config;
 
   const configSchema = new mongoose.Schema({
     ns: { type: String, required: true, index: true },
@@ -23,21 +12,15 @@ module.exports = function(crowi) {
     value: { type: String, required: true },
   });
 
-  function validateCrowi() {
-    if (crowi == null) {
-      throw new Error('"crowi" is null. Init Config model with "crowi" argument first.');
-    }
-  }
-
   /**
    * default values when GROWI is cleanly installed
    */
-  function getArrayForInstalling() {
+  function getConfigsForInstalling() {
     const config = getDefaultCrowiConfigs();
 
     // overwrite
+    config['app:installed'] = true;
     config['app:fileUpload'] = true;
-    config['security:isEnabledPassport'] = true;
     config['customize:behavior'] = 'growi';
     config['customize:layout'] = 'growi';
     config['customize:isSavedStatesOfTabChanges'] = false;
@@ -51,8 +34,8 @@ module.exports = function(crowi) {
   function getDefaultCrowiConfigs() {
     /* eslint-disable key-spacing */
     return {
-      // 'app:installed'     : "0.0.0",
-      'app:confidential'  : '',
+      'app:installed'     : false,
+      'app:confidential'  : undefined,
 
       'app:fileUpload'    : false,
       'app:globalLang'    : 'en-US',
@@ -64,9 +47,8 @@ module.exports = function(crowi) {
 
       'security:list-policy:hideRestrictedByOwner' : false,
       'security:list-policy:hideRestrictedByGroup' : false,
-      'security:pageCompleteDeletionAuthority' : null,
+      'security:pageCompleteDeletionAuthority' : undefined,
 
-      'security:isEnabledPassport' : false,
       'security:passport-ldap:isEnabled' : false,
       'security:passport-ldap:serverUrl' : undefined,
       'security:passport-ldap:isUserBind' : undefined,
@@ -90,24 +72,24 @@ module.exports = function(crowi) {
 
       'aws:bucket'          : 'growi',
       'aws:region'          : 'ap-northeast-1',
-      'aws:accessKeyId'     : '',
-      'aws:secretAccessKey' : '',
+      'aws:accessKeyId'     : undefined,
+      'aws:secretAccessKey' : undefined,
 
-      'mail:from'         : '',
-      'mail:smtpHost'     : '',
-      'mail:smtpPort'     : '',
-      'mail:smtpUser'     : '',
-      'mail:smtpPassword' : '',
+      'mail:from'         : undefined,
+      'mail:smtpHost'     : undefined,
+      'mail:smtpPort'     : undefined,
+      'mail:smtpUser'     : undefined,
+      'mail:smtpPassword' : undefined,
 
-      'google:clientId'     : '',
-      'google:clientSecret' : '',
+      'google:clientId'     : undefined,
+      'google:clientSecret' : undefined,
 
       'plugin:isEnabledPlugins' : true,
 
-      'customize:css' : '',
-      'customize:script' : '',
-      'customize:header' : '',
-      'customize:title' : '',
+      'customize:css' : undefined,
+      'customize:script' : undefined,
+      'customize:header' : undefined,
+      'customize:title' : undefined,
       'customize:highlightJsStyle' : 'github',
       'customize:highlightJsStyleBorder' : false,
       'customize:theme' : 'default',
@@ -118,10 +100,10 @@ module.exports = function(crowi) {
       'customize:isEnabledAttachTitleHeader' : false,
       'customize:showRecentCreatedNumber' : 10,
 
-      'importer:esa:team_name': '',
-      'importer:esa:access_token': '',
-      'importer:qiita:team_name': '',
-      'importer:qiita:access_token': '',
+      'importer:esa:team_name': undefined,
+      'importer:esa:access_token': undefined,
+      'importer:qiita:team_name': undefined,
+      'importer:qiita:access_token': undefined,
     };
     /* eslint-enable key-spacing */
   }
@@ -135,27 +117,24 @@ module.exports = function(crowi) {
       'markdown:isEnabledLinebreaks': false,
       'markdown:isEnabledLinebreaksInComments': true,
       'markdown:presentation:pageBreakSeparator': 1,
-      'markdown:presentation:pageBreakCustomSeparator': '',
+      'markdown:presentation:pageBreakCustomSeparator': undefined,
     };
   }
 
-  function getValueForCrowiNS(config, key) {
-    // return the default value if undefined
-    if (undefined === config.crowi || undefined === config.crowi[key]) {
-      return getDefaultCrowiConfigs()[key];
-    }
-
-    return config.crowi[key];
+  function getDefaultNotificationConfigs() {
+    return {
+      'slack:isIncomingWebhookPrioritized': false,
+      'slack:incomingWebhookUrl': undefined,
+      'slack:token': undefined,
+    };
   }
 
-  function getValueForMarkdownNS(config, key) {
-    // return the default value if undefined
-    if (undefined === config.markdown || undefined === config.markdown[key]) {
-      return getDefaultMarkdownConfigs()[key];
-    }
-
-    return config.markdown[key];
-  }
+  /**
+   * It is deprecated to use this for anything other than AppService#isDBInitialized.
+   */
+  configSchema.statics.getConfigsObjectForInstalling = function() {
+    return getConfigsForInstalling();
+  };
 
   /**
    * It is deprecated to use this for anything other than ConfigLoader#load.
@@ -171,472 +150,36 @@ module.exports = function(crowi) {
     return getDefaultMarkdownConfigs();
   };
 
-  configSchema.statics.getRestrictGuestModeLabels = function() {
-    const labels = {};
-    labels[SECURITY_RESTRICT_GUEST_MODE_DENY] = 'security_setting.guest_mode.deny';
-    labels[SECURITY_RESTRICT_GUEST_MODE_READONLY] = 'security_setting.guest_mode.readonly';
-
-    return labels;
-  };
-
-  configSchema.statics.getRegistrationModeLabels = function() {
-    const labels = {};
-    labels[SECURITY_REGISTRATION_MODE_OPEN] = 'security_setting.registration_mode.open';
-    labels[SECURITY_REGISTRATION_MODE_RESTRICTED] = 'security_setting.registration_mode.restricted';
-    labels[SECURITY_REGISTRATION_MODE_CLOSED] = 'security_setting.registration_mode.closed';
-
-    return labels;
-  };
-
-  configSchema.statics.updateConfigCache = function(ns, config) {
-    validateCrowi();
-
-    const originalConfig = crowi.getConfig();
-    const newNSConfig = originalConfig[ns] || {};
-    Object.keys(config).forEach((key) => {
-      if (config[key] || config[key] === '' || config[key] === false) {
-        newNSConfig[key] = config[key];
-      }
-    });
-
-    originalConfig[ns] = newNSConfig;
-    crowi.setConfig(originalConfig);
-
-    // initialize custom css/script
-    Config.initCustomCss(originalConfig);
-    Config.initCustomScript(originalConfig);
-  };
-
-  // Execute only once for installing application
-  configSchema.statics.applicationInstall = function(callback) {
-    const Config = this;
-    Config.count({ ns: 'crowi' }, (err, count) => {
-      if (count > 0) {
-        return callback(new Error('Application already installed'), null);
-      }
-      Config.updateNamespaceByArray('crowi', getArrayForInstalling(), (err, configs) => {
-        Config.updateConfigCache('crowi', configs);
-        return callback(err, configs);
-      });
-    });
-  };
-
-  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) {
-    const Config = this;
-    if (configs.length < 0) {
-      return callback(new Error('Argument #1 is not array.'), null);
-    }
-
-    Object.keys(configs).forEach((key) => {
-      const value = configs[key];
-
-      Config.findOneAndUpdate(
-        { ns, key },
-        { ns, key, value: JSON.stringify(value) },
-        { upsert: true },
-        (err, config) => {
-          debug('Config.findAndUpdate', err, config);
-        },
-      );
-    });
-
-    return callback(null, configs);
-  };
-
-  configSchema.statics.findOneAndUpdateByNsAndKey = async function(ns, key, value) {
-    return this.findOneAndUpdate(
-      { ns, key },
-      { ns, key, value: JSON.stringify(value) },
-      { upsert: true },
-    );
-  };
-
-  configSchema.statics.getConfig = function(callback) {
-  };
-
-  configSchema.statics.loadAllConfig = function(callback) {
-    const Config = this;
-
-
-    const config = {};
-    config.crowi = {}; // crowi namespace
-
-    Config.find()
-      .sort({ ns: 1, key: 1 })
-      .exec((err, doc) => {
-        doc.forEach((el) => {
-          if (!config[el.ns]) {
-            config[el.ns] = {};
-          }
-          config[el.ns][el.key] = JSON.parse(el.value);
-        });
-
-        debug('Config loaded', config);
-
-        // initialize custom css/script
-        Config.initCustomCss(config);
-        Config.initCustomScript(config);
-
-        return callback(null, config);
-      });
-  };
-
-  configSchema.statics.appTitle = function(config) {
-    const key = 'app:title';
-    return getValueForCrowiNS(config, key) || 'GROWI';
-  };
-
-  configSchema.statics.globalLang = function(config) {
-    const key = 'app:globalLang';
-    return getValueForCrowiNS(config, key);
-  };
-
-  configSchema.statics.isEnabledPassport = function(config) {
-    // always true if growi installed cleanly
-    if (Object.keys(config.crowi).length === 0) {
-      return true;
-    }
-
-    const key = 'security:isEnabledPassport';
-    return getValueForCrowiNS(config, key);
-  };
-
-  configSchema.statics.isEnabledPassportLdap = function(config) {
-    const key = 'security:passport-ldap:isEnabled';
-    return getValueForCrowiNS(config, key);
-  };
-
-  configSchema.statics.isEnabledPassportGoogle = function(config) {
-    const key = 'security:passport-google:isEnabled';
-    return getValueForCrowiNS(config, key);
-  };
-
-  configSchema.statics.isEnabledPassportGitHub = function(config) {
-    const key = 'security:passport-github:isEnabled';
-    return getValueForCrowiNS(config, key);
-  };
-
-  configSchema.statics.isEnabledPassportTwitter = function(config) {
-    const key = 'security:passport-twitter:isEnabled';
-    return getValueForCrowiNS(config, key);
-  };
-
-  configSchema.statics.isEnabledPassportOidc = function(config) {
-    const key = 'security:passport-oidc:isEnabled';
-    return getValueForCrowiNS(config, key);
-  };
-
-  configSchema.statics.isEnabledPassportBasic = function(config) {
-    const key = 'security:passport-basic:isEnabled';
-    return getValueForCrowiNS(config, key);
-  };
-
-  configSchema.statics.isUploadable = function(config) {
-    const method = process.env.FILE_UPLOAD || 'aws';
-
-    if (method === 'aws' && (
-      !config.crowi['aws:accessKeyId']
-        || !config.crowi['aws:secretAccessKey']
-        || !config.crowi['aws:region']
-        || !config.crowi['aws:bucket'])) {
-      return false;
-    }
-
-    return method !== 'none';
-  };
-
-  configSchema.statics.isGuestAllowedToRead = function(config) {
-    // return true if puclic wiki mode
-    if (Config.isPublicWikiOnly(config)) {
-      return true;
-    }
-
-    // return false if undefined
-    if (undefined === config.crowi || undefined === config.crowi['security:restrictGuestMode']) {
-      return false;
-    }
-
-    return SECURITY_RESTRICT_GUEST_MODE_READONLY === config.crowi['security:restrictGuestMode'];
-  };
-
-  configSchema.statics.hidePagesRestrictedByOwnerInList = function(config) {
-    const key = 'security:list-policy:hideRestrictedByOwner';
-    return getValueForCrowiNS(config, key);
-  };
-
-  configSchema.statics.hidePagesRestrictedByGroupInList = function(config) {
-    const key = 'security:list-policy:hideRestrictedByGroup';
-    return getValueForCrowiNS(config, key);
-  };
-
-  configSchema.statics.isEnabledPlugins = function(config) {
-    const key = 'plugin:isEnabledPlugins';
-    return getValueForCrowiNS(config, key);
-  };
-
-  configSchema.statics.isEnabledLinebreaks = function(config) {
-    const key = 'markdown:isEnabledLinebreaks';
-    return getValueForMarkdownNS(config, key);
-  };
-
-  configSchema.statics.isEnabledLinebreaksInComments = function(config) {
-    const key = 'markdown:isEnabledLinebreaksInComments';
-    return getValueForMarkdownNS(config, key);
-  };
-  configSchema.statics.isPublicWikiOnly = function(config) {
-    const publicWikiOnly = process.env.PUBLIC_WIKI_ONLY;
-    if (publicWikiOnly === 'true' || publicWikiOnly === 1) {
-      return true;
-    }
-    return false;
-  };
-
-  configSchema.statics.pageBreakSeparator = function(config) {
-    const key = 'markdown:presentation:pageBreakSeparator';
-    return getValueForMarkdownNS(config, key);
-  };
-
-  configSchema.statics.pageBreakCustomSeparator = function(config) {
-    const key = 'markdown:presentation:pageBreakCustomSeparator';
-    return getValueForMarkdownNS(config, key);
-  };
-
-  configSchema.statics.isEnabledXssPrevention = function(config) {
-    const key = 'markdown:xss:isEnabledPrevention';
-    return getValueForMarkdownNS(config, key);
-  };
-
-  configSchema.statics.xssOption = function(config) {
-    const key = 'markdown:xss:option';
-    return getValueForMarkdownNS(config, key);
-  };
-
-  configSchema.statics.tagWhiteList = function(config) {
-    const key = 'markdown:xss:tagWhiteList';
-
-    if (this.isEnabledXssPrevention(config)) {
-      switch (this.xssOption(config)) {
-        case 1: // ignore all: use default option
-          return [];
-
-        case 2: // recommended
-          return recommendedWhitelist.tags;
-
-        case 3: // custom white list
-          return config.markdown[key];
-
-        default:
-          return [];
-      }
-    }
-    else {
-      return [];
-    }
-  };
-
-  configSchema.statics.attrWhiteList = function(config) {
-    const key = 'markdown:xss:attrWhiteList';
-
-    if (this.isEnabledXssPrevention(config)) {
-      switch (this.xssOption(config)) {
-        case 1: // ignore all: use default option
-          return [];
-
-        case 2: // recommended
-          return recommendedWhitelist.attrs;
-
-        case 3: // custom white list
-          return config.markdown[key];
-
-        default:
-          return [];
-      }
-    }
-    else {
-      return [];
-    }
-  };
-
-  /**
-   * initialize custom css strings
-   */
-  configSchema.statics.initCustomCss = function(config) {
-    const key = 'customize:css';
-    const rawCss = getValueForCrowiNS(config, key);
-    // uglify and store
-    this._customCss = uglifycss.processString(rawCss);
-  };
-
-  configSchema.statics.customCss = function(config) {
-    return this._customCss;
-  };
-
-  configSchema.statics.initCustomScript = function(config) {
-    const key = 'customize:script';
-    const rawScript = getValueForCrowiNS(config, key);
-    // store as is
-    this._customScript = rawScript;
-  };
-
-  configSchema.statics.customScript = function(config) {
-    return this._customScript;
-  };
-
-  configSchema.statics.customHeader = function(config) {
-    const key = 'customize:header';
-    return getValueForCrowiNS(config, key);
-  };
-
-  configSchema.statics.theme = function(config) {
-    const key = 'customize:theme';
-    return getValueForCrowiNS(config, key);
-  };
-
-  configSchema.statics.customTitle = function(config, page) {
-    validateCrowi();
-
-    const key = 'customize:title';
-    let customTitle = getValueForCrowiNS(config, key);
-
-    if (customTitle == null || customTitle.trim().length === 0) {
-      customTitle = '{{page}} - {{sitename}}';
-    }
-
-    // replace
-    customTitle = customTitle
-      .replace('{{sitename}}', this.appTitle(config))
-      .replace('{{page}}', page);
-
-    return crowi.xss.process(customTitle);
-  };
-
-  configSchema.statics.behaviorType = function(config) {
-    const key = 'customize:behavior';
-    return getValueForCrowiNS(config, key);
-  };
-
-  configSchema.statics.layoutType = function(config) {
-    const key = 'customize:layout';
-    return getValueForCrowiNS(config, key);
-  };
-
-  configSchema.statics.highlightJsStyle = function(config) {
-    const key = 'customize:highlightJsStyle';
-    return getValueForCrowiNS(config, key);
-  };
-
-  configSchema.statics.highlightJsStyleBorder = function(config) {
-    const key = 'customize:highlightJsStyleBorder';
-    return getValueForCrowiNS(config, key);
-  };
-
-  configSchema.statics.isEnabledTimeline = function(config) {
-    const key = 'customize:isEnabledTimeline';
-    return getValueForCrowiNS(config, key);
-  };
-
-  configSchema.statics.isSavedStatesOfTabChanges = function(config) {
-    const key = 'customize:isSavedStatesOfTabChanges';
-    return getValueForCrowiNS(config, key);
-  };
-
-  configSchema.statics.isEnabledAttachTitleHeader = function(config) {
-    const key = 'customize:isEnabledAttachTitleHeader';
-    return getValueForCrowiNS(config, key);
-  };
-
-  configSchema.statics.showRecentCreatedNumber = function(config) {
-    const key = 'customize:showRecentCreatedNumber';
-    return getValueForCrowiNS(config, key);
-  };
-
-  configSchema.statics.fileUploadEnabled = function(config) {
-    const Config = this;
-
-    if (!Config.isUploadable(config)) {
-      return false;
-    }
-
-    // convert to boolean
-    return !!config.crowi['app:fileUpload'];
-  };
-
-  configSchema.statics.hasSlackConfig = function(config) {
-    return Config.hasSlackToken(config) || Config.hasSlackIwhUrl(config);
-  };
-
   /**
-   * for Slack Incoming Webhooks
+   * It is deprecated to use this for anything other than ConfigLoader#load.
    */
-  configSchema.statics.hasSlackIwhUrl = function(config) {
-    if (!config.notification) {
-      return false;
-    }
-    return (!!config.notification['slack:incomingWebhookUrl']);
-  };
-
-  configSchema.statics.isIncomingWebhookPrioritized = function(config) {
-    if (!config.notification) {
-      return false;
-    }
-    return (!!config.notification['slack:isIncomingWebhookPrioritized']);
-  };
-
-  configSchema.statics.hasSlackToken = function(config) {
-    if (!config.notification) {
-      return false;
-    }
-
-    return (!!config.notification['slack:token']);
+  configSchema.statics.getDefaultNotificationConfigsObject = function() {
+    return getDefaultNotificationConfigs();
   };
 
-  configSchema.statics.getLocalconfig = function(config) {
-    const Config = this;
+  configSchema.statics.getLocalconfig = function() {
     const env = process.env;
 
     const localConfig = {
       crowi: {
-        title: Config.appTitle(crowi),
-        url: crowi.configManager.getSiteUrl(),
+        title: crowi.appService.getAppTitle(),
+        url: crowi.appService.getSiteUrl(),
       },
       upload: {
-        image: Config.isUploadable(config),
-        file: Config.fileUploadEnabled(config),
+        image: crowi.fileUploadService.getIsUploadable(),
+        file: crowi.fileUploadService.getFileUploadEnabled(),
       },
-      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.slackNotificationService.hasSlackConfig(),
       env: {
         PLANTUML_URI: env.PLANTUML_URI || null,
         BLOCKDIAG_URI: env.BLOCKDIAG_URI || null,
@@ -644,45 +187,15 @@ module.exports = function(crowi) {
         MATHJAX: env.MATHJAX || 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;
   };
 
-  configSchema.statics.userUpperLimit = function(crowi) {
-    const key = 'USER_UPPER_LIMIT';
-    const env = crowi.env[key];
-
-    if (undefined === crowi.env || undefined === crowi.env[key]) {
-      return 0;
-    }
-    return Number(env);
-  };
-
-  /*
-  configSchema.statics.isInstalled = function(config)
-  {
-    if (!config.crowi) {
-      return false;
-    }
-
-    if (config.crowi['app:installed']
-       && config.crowi['app:installed'] !== '0.0.0') {
-      return true;
-    }
-
-    return false;
-  }
-  */
-
-  Config = mongoose.model('Config', configSchema);
-  Config.SECURITY_REGISTRATION_MODE_OPEN = SECURITY_REGISTRATION_MODE_OPEN;
-  Config.SECURITY_REGISTRATION_MODE_RESTRICTED = SECURITY_REGISTRATION_MODE_RESTRICTED;
-  Config.SECURITY_REGISTRATION_MODE_CLOSED = SECURITY_REGISTRATION_MODE_CLOSED;
-
+  const Config = mongoose.model('Config', configSchema);
 
   return Config;
 };

+ 1 - 0
src/server/models/index.js

@@ -1,4 +1,5 @@
 module.exports = {
+  Config: require('./config'),
   Page: require('./page'),
   PageTagRelation: require('./page-tag-relation'),
   User: require('./user'),

+ 2 - 5
src/server/models/page.js

@@ -807,12 +807,9 @@ module.exports = function(crowi) {
   async function addConditionToFilteringByViewerForList(builder, user, showAnyoneKnowsLink) {
     validateCrowi();
 
-    const Config = crowi.model('Config');
-    const config = crowi.getConfig();
-
     // determine User condition
-    const hidePagesRestrictedByOwner = Config.hidePagesRestrictedByOwnerInList(config);
-    const hidePagesRestrictedByGroup = Config.hidePagesRestrictedByGroupInList(config);
+    const hidePagesRestrictedByOwner = crowi.configManager.getConfig('crowi', 'security:list-policy:hideRestrictedByOwner');
+    const hidePagesRestrictedByGroup = crowi.configManager.getConfig('crowi', 'security:list-policy:hidePagesRestrictedByGroupInList');
 
     // determine UserGroup condition
     let userGroups = null;

+ 0 - 5
src/server/models/user-group-relation.js

@@ -41,10 +41,6 @@ class UserGroupRelation {
     return this._crowi;
   }
 
-  static init() {
-    this.removeAllInvalidRelations();
-  }
-
   /**
    * remove all invalid relations that has reference to unlinked document
    */
@@ -288,6 +284,5 @@ module.exports = function(crowi) {
   UserGroupRelation.crowi = crowi;
   schema.loadClass(UserGroupRelation);
   const model = mongoose.model('UserGroupRelation', schema);
-  model.init();
   return model;
 };

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

@@ -78,21 +78,20 @@ module.exports = function(crowi) {
   function decideUserStatusOnRegistration() {
     validateCrowi();
 
-    const Config = crowi.model('Config');
+    const { configManager, aclService } = crowi;
 
-
-    const config = crowi.getConfig();
-
-    if (!config.crowi) {
+    const isInstalled = configManager.getConfig('crowi', 'app:installed');
+    if (!isInstalled) {
       return STATUS_ACTIVE; // is this ok?
     }
 
     // status decided depends on registrationMode
-    switch (config.crowi['security:registrationMode']) {
-      case Config.SECURITY_REGISTRATION_MODE_OPEN:
+    const registrationMode = configManager.getConfig('crowi', 'security:registrationMode');
+    switch (registrationMode) {
+      case aclService.labels.SECURITY_REGISTRATION_MODE_OPEN:
         return STATUS_ACTIVE;
-      case Config.SECURITY_REGISTRATION_MODE_RESTRICTED:
-      case Config.SECURITY_REGISTRATION_MODE_CLOSED: // 一応
+      case aclService.labels.SECURITY_REGISTRATION_MODE_RESTRICTED:
+      case aclService.labels.SECURITY_REGISTRATION_MODE_CLOSED: // 一応
         return STATUS_REGISTERED;
       default:
         return STATUS_ACTIVE; // どっちにすんのがいいんだろうな
@@ -360,11 +359,10 @@ module.exports = function(crowi) {
   userSchema.statics.isEmailValid = function(email, callback) {
     validateCrowi();
 
-    const config = crowi.getConfig();
-    const whitelist = config.crowi['security:registrationWhiteList'];
+    const whitelist = crowi.configManager.getConfig('crowi', 'security:registrationWhiteList');
 
     if (Array.isArray(whitelist) && whitelist.length > 0) {
-      return config.crowi['security:registrationWhiteList'].some((allowedEmail) => {
+      return whitelist.some((allowedEmail) => {
         const re = new RegExp(`${allowedEmail}$`);
         return re.test(email);
       });
@@ -523,8 +521,9 @@ module.exports = function(crowi) {
   };
 
   userSchema.statics.isUserCountExceedsUpperLimit = async function() {
-    const Config = crowi.model('Config');
-    const userUpperLimit = Config.userUpperLimit(crowi);
+    const { aclService } = crowi;
+
+    const userUpperLimit = aclService.userUpperLimit();
     if (userUpperLimit === 0) {
       return false;
     }
@@ -632,12 +631,12 @@ module.exports = function(crowi) {
   userSchema.statics.createUsersByInvitation = function(emailList, toSendEmail, callback) {
     validateCrowi();
 
+    const configManager = crowi.configManager;
+
     const User = this;
     const createdUserList = [];
-    const Config = crowi.model('Config');
-    const config = crowi.getConfig();
-
     const mailer = crowi.getMailer();
+
     if (!Array.isArray(emailList)) {
       debug('emailList is not array');
     }
@@ -677,7 +676,7 @@ module.exports = function(crowi) {
           newUser.createdAt = Date.now();
           newUser.status = STATUS_INVITED;
 
-          const globalLang = Config.globalLang(config);
+          const globalLang = configManager.getConfig('crowi', 'app:globalLang');
           if (globalLang != null) {
             newUser.lang = globalLang;
           }
@@ -718,15 +717,17 @@ module.exports = function(crowi) {
                 return next();
               }
 
+              const appTitle = crowi.appService.getAppTitle();
+
               mailer.send({
                 to: user.email,
-                subject: `Invitation to ${Config.appTitle(config)}`,
+                subject: `Invitation to ${appTitle}`,
                 template: path.join(crowi.localeDir, 'en-US/admin/userInvitation.txt'),
                 vars: {
                   email: user.email,
                   password: user.password,
-                  url: crowi.configManager.getSiteUrl(),
-                  appTitle: Config.appTitle(config),
+                  url: crowi.appService.getSiteUrl(),
+                  appTitle,
                 },
               },
               (err, s) => {
@@ -771,9 +772,8 @@ module.exports = function(crowi) {
       newUser.setPassword(password);
     }
 
-    const Config = crowi.model('Config');
-    const config = crowi.getConfig();
-    const globalLang = Config.globalLang(config);
+    const configManager = crowi.configManager;
+    const globalLang = configManager.getConfig('crowi', 'app:globalLang');
     if (globalLang != null) {
       newUser.lang = globalLang;
     }

+ 103 - 177
src/server/routes/admin.js

@@ -9,11 +9,17 @@ module.exports = function(crowi, app) {
   const ExternalAccount = models.ExternalAccount;
   const UserGroup = models.UserGroup;
   const UserGroupRelation = models.UserGroupRelation;
-  const Config = models.Config;
   const GlobalNotificationSetting = models.GlobalNotificationSetting;
   const GlobalNotificationMailSetting = models.GlobalNotificationMailSetting;
   const GlobalNotificationSlackSetting = models.GlobalNotificationSlackSetting; // eslint-disable-line no-unused-vars
 
+  const {
+    configManager,
+    aclService,
+    slackNotificationService,
+    customizeService,
+  } = crowi;
+
   const recommendedWhitelist = require('@commons/service/xss/recommended-whitelist');
   const PluginUtils = require('../plugins/plugin-utils');
   const ApiResponse = require('../util/apiResponse');
@@ -90,11 +96,7 @@ module.exports = function(crowi, app) {
   // app.get('/admin/app'                  , admin.app.index);
   actions.app = {};
   actions.app.index = function(req, res) {
-    const settingForm = Config.setupConfigFormData('crowi', req.config);
-
-    return res.render('admin/app', {
-      settingForm,
-    });
+    return res.render('admin/app');
   };
 
   actions.app.settingUpdate = function(req, res) {
@@ -103,16 +105,13 @@ module.exports = function(crowi, app) {
   // app.get('/admin/security'                  , admin.security.index);
   actions.security = {};
   actions.security.index = function(req, res) {
-    const settingForm = Config.setupConfigFormData('crowi', req.config);
-    const isAclEnabled = !Config.isPublicWikiOnly(req.config);
-    return res.render('admin/security', { settingForm, isAclEnabled });
+    return res.render('admin/security');
   };
 
   // app.get('/admin/markdown'                  , admin.markdown.index);
   actions.markdown = {};
   actions.markdown.index = function(req, res) {
-    const config = crowi.getConfig();
-    const markdownSetting = Config.setupConfigFormData('markdown', config);
+    const markdownSetting = configManager.getConfigByPrefix('markdown', 'markdown:');
 
     return res.render('admin/markdown', {
       markdownSetting,
@@ -121,66 +120,54 @@ module.exports = function(crowi, app) {
   };
 
   // app.post('/admin/markdown/lineBreaksSetting' , admin.markdown.lineBreaksSetting);
-  actions.markdown.lineBreaksSetting = function(req, res) {
+  actions.markdown.lineBreaksSetting = async function(req, res) {
     const markdownSetting = req.form.markdownSetting;
 
-    req.session.markdownSetting = markdownSetting;
     if (req.form.isValid) {
-      Config.updateNamespaceByArray('markdown', markdownSetting, (err, config) => {
-        Config.updateConfigCache('markdown', config);
-        req.session.markdownSetting = null;
-        req.flash('successMessage', ['Successfully updated!']);
-        return res.redirect('/admin/markdown');
-      });
+      await configManager.updateConfigsInTheSameNamespace('markdown', markdownSetting);
+      req.flash('successMessage', ['Successfully updated!']);
     }
     else {
       req.flash('errorMessage', req.form.errors);
-      return res.redirect('/admin/markdown');
     }
+
+    return res.redirect('/admin/markdown');
   };
 
   // app.post('/admin/markdown/presentationSetting' , admin.markdown.presentationSetting);
-  actions.markdown.presentationSetting = function(req, res) {
-    const presentationSetting = req.form.markdownSetting;
+  actions.markdown.presentationSetting = async function(req, res) {
+    const markdownSetting = req.form.markdownSetting;
 
-    req.session.markdownSetting = presentationSetting;
     if (req.form.isValid) {
-      Config.updateNamespaceByArray('markdown', presentationSetting, (err, config) => {
-        Config.updateConfigCache('markdown', config);
-        req.session.markdownSetting = null;
-        req.flash('successMessage', ['Successfully updated!']);
-        return res.redirect('/admin/markdown');
-      });
+      await configManager.updateConfigsInTheSameNamespace('markdown', markdownSetting);
+      req.flash('successMessage', ['Successfully updated!']);
     }
     else {
       req.flash('errorMessage', req.form.errors);
-      return res.redirect('/admin/markdown');
     }
+
+    return res.redirect('/admin/markdown');
   };
 
   // app.post('/admin/markdown/xss-setting' , admin.markdown.xssSetting);
-  actions.markdown.xssSetting = function(req, res) {
+  actions.markdown.xssSetting = async function(req, res) {
     const xssSetting = req.form.markdownSetting;
 
-    xssSetting['markdown:xss:tagWhiteList'] = stringToArray(xssSetting['markdown:xss:tagWhiteList']);
-    xssSetting['markdown:xss:attrWhiteList'] = stringToArray(xssSetting['markdown:xss:attrWhiteList']);
+    xssSetting['markdown:xss:tagWhiteList'] = csvToArray(xssSetting['markdown:xss:tagWhiteList']);
+    xssSetting['markdown:xss:attrWhiteList'] = csvToArray(xssSetting['markdown:xss:attrWhiteList']);
 
-    req.session.markdownSetting = xssSetting;
     if (req.form.isValid) {
-      Config.updateNamespaceByArray('markdown', xssSetting, (err, config) => {
-        Config.updateConfigCache('markdown', config);
-        req.session.xssSetting = null;
-        req.flash('successMessage', ['Successfully updated!']);
-        return res.redirect('/admin/markdown');
-      });
+      await configManager.updateConfigsInTheSameNamespace('markdown', xssSetting);
+      req.flash('successMessage', ['Successfully updated!']);
     }
     else {
       req.flash('errorMessage', req.form.errors);
-      return res.redirect('/admin/markdown');
     }
+
+    return res.redirect('/admin/markdown');
   };
 
-  const stringToArray = (string) => {
+  const csvToArray = (string) => {
     const array = string.split(',');
     return array.map((item) => { return item.trim() });
   };
@@ -188,7 +175,7 @@ module.exports = function(crowi, app) {
   // app.get('/admin/customize' , admin.customize.index);
   actions.customize = {};
   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 */
     const highlightJsCssSelectorOptions = {
@@ -214,13 +201,12 @@ module.exports = function(crowi, app) {
   // app.get('/admin/notification'               , admin.notification.index);
   actions.notification = {};
   actions.notification.index = async(req, res) => {
-    const config = crowi.getConfig();
     const UpdatePost = crowi.model('UpdatePost');
-    let slackSetting = Config.setupConfigFormData('notification', config);
-    const hasSlackIwhUrl = Config.hasSlackIwhUrl(config);
-    const hasSlackToken = Config.hasSlackToken(config);
+    let slackSetting = configManager.getConfigByPrefix('notification', 'slack:');
+    const hasSlackIwhUrl = !!configManager.getConfig('notification', 'slack:incomingWebhookUrl');
+    const hasSlackToken = !!configManager.getConfig('notification', 'slack:token');
 
-    if (!Config.hasSlackIwhUrl(req.config)) {
+    if (!hasSlackIwhUrl) {
       slackSetting['slack:incomingWebhookUrl'] = '';
     }
 
@@ -242,51 +228,46 @@ module.exports = function(crowi, app) {
   };
 
   // app.post('/admin/notification/slackSetting' , admin.notification.slackauth);
-  actions.notification.slackSetting = function(req, res) {
+  actions.notification.slackSetting = async function(req, res) {
     const slackSetting = req.form.slackSetting;
 
-    req.session.slackSetting = slackSetting;
     if (req.form.isValid) {
-      Config.updateNamespaceByArray('notification', slackSetting, (err, config) => {
-        Config.updateConfigCache('notification', config);
-        req.flash('successMessage', ['Successfully Updated!']);
-        req.session.slackSetting = null;
-
-        // Re-setup
-        crowi.setupSlack().then(() => {
-          return res.redirect('/admin/notification');
-        });
+      await configManager.updateConfigsInTheSameNamespace('notification', slackSetting);
+      req.flash('successMessage', ['Successfully Updated!']);
+
+      // Re-setup
+      crowi.setupSlack().then(() => {
       });
     }
     else {
       req.flash('errorMessage', req.form.errors);
-      return res.redirect('/admin/notification');
     }
+
+    return res.redirect('/admin/notification');
   };
 
   // app.get('/admin/notification/slackAuth'     , admin.notification.slackauth);
   actions.notification.slackAuth = function(req, res) {
     const code = req.query.code;
 
-    if (!code || !Config.hasSlackConfig(req.config)) {
+    if (!code || !slackNotificationService.hasSlackConfig()) {
       return res.redirect('/admin/notification');
     }
 
     const slack = crowi.slack;
     slack.getOauthAccessToken(code)
-      .then((data) => {
+      .then(async(data) => {
         debug('oauth response', data);
-        Config.updateNamespaceByArray('notification', { 'slack:token': data.access_token }, (err, config) => {
-          if (err) {
-            req.flash('errorMessage', ['Failed to save access_token. Please try again.']);
-          }
-          else {
-            Config.updateConfigCache('notification', config);
-            req.flash('successMessage', ['Successfully Connected!']);
-          }
 
-          return res.redirect('/admin/notification');
-        });
+        try {
+          await configManager.updateConfigsInTheSameNamespace('notification', { 'slack:token': data.access_token });
+          req.flash('successMessage', ['Successfully Connected!']);
+        }
+        catch (err) {
+          req.flash('errorMessage', ['Failed to save access_token. Please try again.']);
+        }
+
+        return res.redirect('/admin/notification');
       })
       .catch((err) => {
         debug('oauth response ERROR', err);
@@ -296,18 +277,16 @@ module.exports = function(crowi, app) {
   };
 
   // app.post('/admin/notification/slackIwhSetting' , admin.notification.slackIwhSetting);
-  actions.notification.slackIwhSetting = function(req, res) {
+  actions.notification.slackIwhSetting = async function(req, res) {
     const slackIwhSetting = req.form.slackIwhSetting;
 
     if (req.form.isValid) {
-      Config.updateNamespaceByArray('notification', slackIwhSetting, (err, config) => {
-        Config.updateConfigCache('notification', config);
-        req.flash('successMessage', ['Successfully Updated!']);
+      await configManager.updateConfigsInTheSameNamespace('notification', slackIwhSetting);
+      req.flash('successMessage', ['Successfully Updated!']);
 
-        // Re-setup
-        crowi.setupSlack().then(() => {
-          return res.redirect('/admin/notification#slack-incoming-webhooks');
-        });
+      // Re-setup
+      crowi.setupSlack().then(() => {
+        return res.redirect('/admin/notification#slack-incoming-webhooks');
       });
     }
     else {
@@ -317,13 +296,11 @@ module.exports = function(crowi, app) {
   };
 
   // app.post('/admin/notification/slackSetting/disconnect' , admin.notification.disconnectFromSlack);
-  actions.notification.disconnectFromSlack = function(req, res) {
-    Config.updateNamespaceByArray('notification', { 'slack:token': '' }, (err, config) => {
-      Config.updateConfigCache('notification', config);
-      req.flash('successMessage', ['Successfully Disconnected!']);
+  actions.notification.disconnectFromSlack = async function(req, res) {
+    await configManager.updateConfigsInTheSameNamespace('notification', { 'slack:token': '' });
+    req.flash('successMessage', ['Successfully Disconnected!']);
 
-      return res.redirect('/admin/notification');
-    });
+    return res.redirect('/admin/notification');
   };
 
   actions.globalNotification = {};
@@ -430,8 +407,7 @@ module.exports = function(crowi, app) {
   actions.user = {};
   actions.user.index = async function(req, res) {
     const activeUsers = await User.countListByStatus(User.STATUS_ACTIVE);
-    const Config = crowi.model('Config');
-    const userUpperLimit = Config.userUpperLimit(crowi);
+    const userUpperLimit = aclService.userUpperLimit();
     const isUserCountExceedsUpperLimit = await User.isUserCountExceedsUpperLimit();
 
     const page = parseInt(req.query.page) || 1;
@@ -648,7 +624,7 @@ module.exports = function(crowi, app) {
   actions.userGroup = {};
   actions.userGroup.index = function(req, res) {
     const page = parseInt(req.query.page) || 1;
-    const isAclEnabled = !Config.isPublicWikiOnly(req.config);
+    const isAclEnabled = aclService.getIsPublicWikiOnly();
     const renderVar = {
       userGroups: [],
       userGroupRelations: new Map(),
@@ -851,7 +827,7 @@ module.exports = function(crowi, app) {
   // Importer management
   actions.importer = {};
   actions.importer.index = function(req, res) {
-    const settingForm = Config.setupConfigFormData('crowi', req.config);
+    const settingForm = configManager.getConfigByPrefix('crowi', 'importer:');
 
     return res.render('admin/importer', {
       settingForm,
@@ -859,7 +835,7 @@ module.exports = function(crowi, app) {
   };
 
   actions.api = {};
-  actions.api.appSetting = function(req, res) {
+  actions.api.appSetting = async function(req, res) {
     const form = req.form.settingForm;
 
     if (req.form.isValid) {
@@ -867,18 +843,20 @@ module.exports = function(crowi, app) {
 
       // mail setting ならここで validation
       if (form['mail:from']) {
-        validateMailSetting(req, form, (err, data) => {
+        validateMailSetting(req, form, async(err, data) => {
           debug('Error validate mail setting: ', err, data);
           if (err) {
             req.form.errors.push('SMTPを利用したテストメール送信に失敗しました。設定をみなおしてください。');
             return res.json({ status: false, message: req.form.errors.join('\n') });
           }
 
-          return saveSetting(req, res, form);
+          await configManager.updateConfigsInTheSameNamespace('crowi', form);
+          return res.json({ status: true });
         });
       }
       else {
-        return saveSetting(req, res, form);
+        await configManager.updateConfigsInTheSameNamespace('crowi', form);
+        return res.json({ status: true });
       }
     }
     else {
@@ -896,7 +874,7 @@ module.exports = function(crowi, app) {
     debug('form content', form);
 
     try {
-      await crowi.configManager.updateConfigsInTheSameNamespace('crowi', form);
+      await configManager.updateConfigsInTheSameNamespace('crowi', form);
       return res.json({ status: true });
     }
     catch (err) {
@@ -911,15 +889,7 @@ module.exports = function(crowi, app) {
     }
 
     const form = req.form.settingForm;
-    const config = crowi.getConfig();
-    const isPublicWikiOnly = Config.isPublicWikiOnly(config);
-    if (isPublicWikiOnly) {
-      const basicName = form['security:basicName'];
-      const basicSecret = form['security:basicSecret'];
-      if (basicName !== '' || basicSecret !== '') {
-        req.form.errors.push('Public Wikiのため、Basic認証は利用できません。');
-        return res.json({ status: false, message: req.form.errors.join('\n') });
-      }
+    if (aclService.getIsPublicWikiOnly()) {
       const guestMode = form['security:restrictGuestMode'];
       if (guestMode === 'Deny') {
         req.form.errors.push('Private Wikiへの設定変更はできません。');
@@ -928,7 +898,7 @@ module.exports = function(crowi, app) {
     }
 
     try {
-      await crowi.configManager.updateConfigsInTheSameNamespace('crowi', form);
+      await configManager.updateConfigsInTheSameNamespace('crowi', form);
       return res.json({ status: true });
     }
     catch (err) {
@@ -945,14 +915,12 @@ module.exports = function(crowi, app) {
     }
 
     debug('form content', form);
-    return saveSettingAsync(form)
+    return configManager.updateConfigsInTheSameNamespace('crowi', form)
       .then(() => {
-        const config = crowi.getConfig();
-
         // reset strategy
         crowi.passportService.resetLdapStrategy();
         // setup strategy
-        if (Config.isEnabledPassportLdap(config)) {
+        if (configManager.getConfig('crowi', 'security:passport-ldap:isEnabled')) {
           crowi.passportService.setupLdapStrategy(true);
         }
         return;
@@ -972,12 +940,12 @@ module.exports = function(crowi, app) {
     }
 
     debug('form content', form);
-    await crowi.configManager.updateConfigsInTheSameNamespace('crowi', form);
+    await configManager.updateConfigsInTheSameNamespace('crowi', form);
 
     // reset strategy
     await crowi.passportService.resetSamlStrategy();
     // setup strategy
-    if (crowi.configManager.getConfig('crowi', 'security:passport-saml:isEnabled')) {
+    if (configManager.getConfig('crowi', 'security:passport-saml:isEnabled')) {
       try {
         await crowi.passportService.setupSamlStrategy(true);
       }
@@ -999,13 +967,12 @@ module.exports = function(crowi, app) {
     }
 
     debug('form content', form);
-    await saveSettingAsync(form);
-    const config = await crowi.getConfig();
+    await configManager.updateConfigsInTheSameNamespace('crowi', form);
 
     // reset strategy
     await crowi.passportService.resetBasicStrategy();
     // setup strategy
-    if (Config.isEnabledPassportBasic(config)) {
+    if (configManager.getConfig('crowi', 'security:passport-basic:isEnabled')) {
       try {
         await crowi.passportService.setupBasicStrategy(true);
       }
@@ -1027,13 +994,12 @@ module.exports = function(crowi, app) {
     }
 
     debug('form content', form);
-    await saveSettingAsync(form);
-    const config = await crowi.getConfig();
+    await configManager.updateConfigsInTheSameNamespace('crowi', form);
 
     // reset strategy
     await crowi.passportService.resetGoogleStrategy();
     // setup strategy
-    if (Config.isEnabledPassportGoogle(config)) {
+    if (configManager.getConfig('crowi', 'security:passport-google:isEnabled')) {
       try {
         await crowi.passportService.setupGoogleStrategy(true);
       }
@@ -1055,13 +1021,12 @@ module.exports = function(crowi, app) {
     }
 
     debug('form content', form);
-    await saveSettingAsync(form);
-    const config = await crowi.getConfig();
+    await configManager.updateConfigsInTheSameNamespace('crowi', form);
 
     // reset strategy
     await crowi.passportService.resetGitHubStrategy();
     // setup strategy
-    if (Config.isEnabledPassportGitHub(config)) {
+    if (configManager.getConfig('crowi', 'security:passport-github:isEnabled')) {
       try {
         await crowi.passportService.setupGitHubStrategy(true);
       }
@@ -1083,13 +1048,12 @@ module.exports = function(crowi, app) {
     }
 
     debug('form content', form);
-    await saveSettingAsync(form);
-    const config = await crowi.getConfig();
+    await configManager.updateConfigsInTheSameNamespace('crowi', form);
 
     // reset strategy
     await crowi.passportService.resetTwitterStrategy();
     // setup strategy
-    if (Config.isEnabledPassportTwitter(config)) {
+    if (configManager.getConfig('crowi', 'security:passport-twitter:isEnabled')) {
       try {
         await crowi.passportService.setupTwitterStrategy(true);
       }
@@ -1111,14 +1075,12 @@ module.exports = function(crowi, app) {
     }
 
     debug('form content', form);
-    await saveSettingAsync(form);
-    const config = await crowi.getConfig();
-
+    await configManager.updateConfigsInTheSameNamespace('crowi', form);
 
     // reset strategy
     await crowi.passportService.resetOidcStrategy();
     // setup strategy
-    if (Config.isEnabledPassportOidc(config)) {
+    if (configManager.getConfig('crowi', 'security:passport-oidc:isEnabled')) {
       try {
         await crowi.passportService.setupOidcStrategy(true);
       }
@@ -1132,23 +1094,16 @@ module.exports = function(crowi, app) {
     return res.json({ status: true });
   };
 
-  actions.api.customizeSetting = function(req, res) {
+  actions.api.customizeSetting = async function(req, res) {
     const form = req.form.settingForm;
 
     if (req.form.isValid) {
       debug('form content', form);
-      return saveSetting(req, res, form);
-    }
-
-    return res.json({ status: false, message: req.form.errors.join('\n') });
-  };
+      await configManager.updateConfigsInTheSameNamespace('crowi', form);
+      customizeService.initCustomCss();
+      customizeService.initCustomTitle();
 
-  actions.api.customizeSetting = function(req, res) {
-    const form = req.form.settingForm;
-
-    if (req.form.isValid) {
-      debug('form content', form);
-      return saveSetting(req, res, form);
+      return res.json({ status: true });
     }
 
     return res.json({ status: false, message: req.form.errors.join('\n') });
@@ -1241,8 +1196,10 @@ module.exports = function(crowi, app) {
       return res.json({ status: false, message: req.form.errors.join('\n') });
     }
 
-    await saveSetting(req, res, form);
-    await importer.initializeEsaClient();
+    await configManager.updateConfigsInTheSameNamespace('crowi', form);
+    importer.initializeEsaClient(); // let it run in the back aftert res
+
+    return res.json({ status: true });
   };
 
   /**
@@ -1258,8 +1215,10 @@ module.exports = function(crowi, app) {
       return res.json({ status: false, message: req.form.errors.join('\n') });
     }
 
-    await saveSetting(req, res, form);
-    await importer.initializeQiitaClient();
+    await configManager.updateConfigsInTheSameNamespace('crowi', form);
+    importer.initializeQiitaClient(); // let it run in the back aftert res
+
+    return res.json({ status: true });
   };
 
   /**
@@ -1394,39 +1353,6 @@ module.exports = function(crowi, app) {
     }
   };
 
-  /**
-   * save settings, update config cache, and response json
-   *
-   * @param {any} req
-   * @param {any} res
-   * @param {any} form
-   */
-  function saveSetting(req, res, form) {
-    Config.updateNamespaceByArray('crowi', form, (err, config) => {
-      Config.updateConfigCache('crowi', config);
-      return res.json({ status: true });
-    });
-  }
-
-  /**
-   * save settings, update config cache ONLY. (this method don't response json)
-   *
-   * @param {any} form
-   * @returns
-   */
-  function saveSettingAsync(form) {
-    return new Promise((resolve, reject) => {
-      Config.updateNamespaceByArray('crowi', form, (err, config) => {
-        if (err) {
-          return reject(err);
-        }
-
-        Config.updateConfigCache('crowi', config);
-        return resolve();
-      });
-    });
-  }
-
   function validateMailSetting(req, form, callback) {
     const mailer = crowi.mailer;
     const option = {
@@ -1463,7 +1389,7 @@ module.exports = function(crowi, app) {
   function validateSamlSettingForm(form, t) {
     for (const key of crowi.passportService.mandatoryConfigKeysForSaml) {
       const formValue = form.settingForm[key];
-      if (crowi.configManager.getConfigFromEnvVars('crowi', key) === null && formValue === '') {
+      if (configManager.getConfigFromEnvVars('crowi', key) === null && formValue === '') {
         const formItemName = t(`security_setting.form_item_name.${key}`);
         form.errors.push(t('form_validation.required', formItemName));
       }

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

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

+ 158 - 172
src/server/routes/index.js

@@ -4,7 +4,7 @@ const autoReap = require('multer-autoreap');
 autoReap.options.reapOnError = true; // continue reaping the file even if an error occurs
 
 module.exports = function(crowi, app) {
-  const middleware = require('../util/middlewares');
+  const middlewares = require('../util/middlewares')(crowi, app);
   const uploads = multer({ dest: `${crowi.tmpDir}uploads` });
   const form = require('../form');
   const page = require('./page')(crowi, app);
@@ -22,62 +22,51 @@ module.exports = function(crowi, app) {
   const revision = require('./revision')(crowi, app);
   const search = require('./search')(crowi, app);
   const hackmd = require('./hackmd')(crowi, app);
-  const loginRequired = middleware.loginRequired;
-  const accessTokenParser = middleware.accessTokenParser(crowi, app);
-  const csrf = middleware.csrfVerify(crowi, app);
-  const config = crowi.getConfig();
-  const Config = crowi.model('Config');
+  const {
+    loginRequired,
+    adminRequired,
+    accessTokenParser,
+    csrfVerify: csrf,
+  } = middlewares;
 
   /* eslint-disable max-len, comma-spacing, no-multi-spaces */
 
-  app.get('/'                        , middleware.applicationInstalled(), loginRequired(crowi, app, false) , page.showTopPage);
+  app.get('/'                        , middlewares.applicationInstalled, loginRequired(false) , page.showTopPage);
 
-  app.get('/installer'               , middleware.applicationNotInstalled() , installer.index);
-  app.post('/installer'              , middleware.applicationNotInstalled() , form.register , csrf, installer.install);
+  app.get('/installer'               , middlewares.applicationNotInstalled , installer.index);
+  app.post('/installer'              , middlewares.applicationNotInstalled , form.register , csrf, installer.install);
 
   app.get('/login/error/:reason'     , login.error);
-  app.get('/login'                   , middleware.applicationInstalled()    , login.login);
+  app.get('/login'                   , middlewares.applicationInstalled    , login.login);
   app.get('/login/invited'           , login.invited);
   app.post('/login/activateInvited'  , form.invited                         , csrf, login.invited);
-
-  // switch POST /login route
-  if (Config.isEnabledPassport(config)) {
-    app.post('/login'                , form.login                           , csrf, loginPassport.loginWithLocal, loginPassport.loginWithLdap, loginPassport.loginFailure);
-    app.post('/_api/login/testLdap'  , loginRequired(crowi, app) , form.login , loginPassport.testLdapCredentials);
-  }
-  else {
-    app.post('/login'                , form.login                           , csrf, login.login);
-  }
+  app.post('/login'                  , form.login                           , csrf, loginPassport.loginWithLocal, loginPassport.loginWithLdap, loginPassport.loginFailure);
+  app.post('/_api/login/testLdap'    , loginRequired() , form.login , loginPassport.testLdapCredentials);
 
   app.post('/register'               , form.register                        , csrf, login.register);
-  app.get('/register'                , middleware.applicationInstalled()    , login.register);
-  app.post('/register/google'        , login.registerGoogle);
-  app.get('/google/callback'         , login.googleCallback);
-  app.get('/login/google'            , login.loginGoogle);
+  app.get('/register'                , middlewares.applicationInstalled    , login.register);
   app.get('/logout'                  , logout.logout);
 
-  app.get('/admin'                          , loginRequired(crowi, app) , middleware.adminRequired() , admin.index);
-  app.get('/admin/app'                      , loginRequired(crowi, app) , middleware.adminRequired() , admin.app.index);
-  app.post('/_api/admin/settings/app'       , loginRequired(crowi, app) , middleware.adminRequired() , csrf, form.admin.app, admin.api.appSetting);
-  app.post('/_api/admin/settings/siteUrl'   , loginRequired(crowi, app) , middleware.adminRequired() , csrf, form.admin.siteUrl, admin.api.asyncAppSetting);
-  app.post('/_api/admin/settings/mail'      , loginRequired(crowi, app) , middleware.adminRequired() , csrf, form.admin.mail, admin.api.appSetting);
-  app.post('/_api/admin/settings/aws'       , loginRequired(crowi, app) , middleware.adminRequired() , csrf, form.admin.aws, admin.api.appSetting);
-  app.post('/_api/admin/settings/plugin'    , loginRequired(crowi, app) , middleware.adminRequired() , csrf, form.admin.plugin, admin.api.appSetting);
+  app.get('/admin'                          , loginRequired() , adminRequired , admin.index);
+  app.get('/admin/app'                      , loginRequired() , adminRequired , admin.app.index);
+  app.post('/_api/admin/settings/app'       , loginRequired() , adminRequired , csrf, form.admin.app, admin.api.appSetting);
+  app.post('/_api/admin/settings/siteUrl'   , loginRequired() , adminRequired , csrf, form.admin.siteUrl, admin.api.asyncAppSetting);
+  app.post('/_api/admin/settings/mail'      , loginRequired() , adminRequired , csrf, form.admin.mail, admin.api.appSetting);
+  app.post('/_api/admin/settings/aws'       , loginRequired() , adminRequired , csrf, form.admin.aws, admin.api.appSetting);
+  app.post('/_api/admin/settings/plugin'    , loginRequired() , adminRequired , csrf, form.admin.plugin, admin.api.appSetting);
 
   // security admin
-  app.get('/admin/security'                     , loginRequired(crowi, app) , middleware.adminRequired() , admin.security.index);
-  app.post('/_api/admin/security/general'       , loginRequired(crowi, app) , middleware.adminRequired() , form.admin.securityGeneral, admin.api.securitySetting);
-  app.post('/_api/admin/security/google'        , loginRequired(crowi, app) , middleware.adminRequired() , csrf, form.admin.securityGoogle, admin.api.securitySetting);
-  app.post('/_api/admin/security/mechanism'     , loginRequired(crowi, app) , middleware.adminRequired() , csrf, form.admin.securityMechanism, admin.api.securitySetting);
-  app.post('/_api/admin/security/passport-ldap' , loginRequired(crowi, app) , middleware.adminRequired() , csrf, form.admin.securityPassportLdap, admin.api.securityPassportLdapSetting);
-  app.post('/_api/admin/security/passport-saml' , loginRequired(crowi, app) , middleware.adminRequired() , csrf, form.admin.securityPassportSaml, admin.api.securityPassportSamlSetting);
-  app.post('/_api/admin/security/passport-basic', loginRequired(crowi, app) , middleware.adminRequired() , csrf, form.admin.securityPassportBasic, admin.api.securityPassportBasicSetting);
+  app.get('/admin/security'                     , loginRequired() , adminRequired , admin.security.index);
+  app.post('/_api/admin/security/general'       , loginRequired() , adminRequired , form.admin.securityGeneral, admin.api.securitySetting);
+  app.post('/_api/admin/security/passport-ldap' , loginRequired() , adminRequired , csrf, form.admin.securityPassportLdap, admin.api.securityPassportLdapSetting);
+  app.post('/_api/admin/security/passport-saml' , loginRequired() , adminRequired , csrf, form.admin.securityPassportSaml, admin.api.securityPassportSamlSetting);
+  app.post('/_api/admin/security/passport-basic' , loginRequired() , adminRequired , csrf, form.admin.securityPassportBasic, admin.api.securityPassportBasicSetting);
 
   // OAuth
-  app.post('/_api/admin/security/passport-google' , loginRequired(crowi, app) , middleware.adminRequired() , csrf, form.admin.securityPassportGoogle, admin.api.securityPassportGoogleSetting);
-  app.post('/_api/admin/security/passport-github' , loginRequired(crowi, app) , middleware.adminRequired() , csrf, form.admin.securityPassportGitHub, admin.api.securityPassportGitHubSetting);
-  app.post('/_api/admin/security/passport-twitter', loginRequired(crowi, app) , middleware.adminRequired() , csrf, form.admin.securityPassportTwitter, admin.api.securityPassportTwitterSetting);
-  app.post('/_api/admin/security/passport-oidc'   , loginRequired(crowi, app) , middleware.adminRequired() , csrf, form.admin.securityPassportOidc, admin.api.securityPassportOidcSetting);
+  app.post('/_api/admin/security/passport-google' , loginRequired() , adminRequired , csrf, form.admin.securityPassportGoogle, admin.api.securityPassportGoogleSetting);
+  app.post('/_api/admin/security/passport-github' , loginRequired() , adminRequired , csrf, form.admin.securityPassportGitHub, admin.api.securityPassportGitHubSetting);
+  app.post('/_api/admin/security/passport-twitter', loginRequired() , adminRequired , csrf, form.admin.securityPassportTwitter, admin.api.securityPassportTwitterSetting);
+  app.post('/_api/admin/security/passport-oidc',    loginRequired() , adminRequired , csrf, form.admin.securityPassportOidc, admin.api.securityPassportOidcSetting);
   app.get('/passport/google'                      , loginPassport.loginWithGoogle);
   app.get('/passport/github'                      , loginPassport.loginWithGitHub);
   app.get('/passport/twitter'                     , loginPassport.loginWithTwitter);
@@ -91,160 +80,157 @@ module.exports = function(crowi, app) {
   app.post('/passport/saml/callback'              , loginPassport.loginPassportSamlCallback);
 
   // markdown admin
-  app.get('/admin/markdown'                   , loginRequired(crowi, app) , middleware.adminRequired() , admin.markdown.index);
-  app.post('/admin/markdown/lineBreaksSetting', loginRequired(crowi, app) , middleware.adminRequired() , csrf, form.admin.markdown, admin.markdown.lineBreaksSetting); // change form name
-  app.post('/admin/markdown/xss-setting'      , loginRequired(crowi, app) , middleware.adminRequired() , csrf, form.admin.markdownXss, admin.markdown.xssSetting);
-  app.post('/admin/markdown/presentationSetting', loginRequired(crowi, app) , middleware.adminRequired() , csrf, form.admin.markdownPresentation, admin.markdown.presentationSetting);
+  app.get('/admin/markdown'                   , loginRequired() , adminRequired , admin.markdown.index);
+  app.post('/admin/markdown/lineBreaksSetting', loginRequired() , adminRequired , csrf, form.admin.markdown, admin.markdown.lineBreaksSetting); // change form name
+  app.post('/admin/markdown/xss-setting'      , loginRequired() , adminRequired , csrf, form.admin.markdownXss, admin.markdown.xssSetting);
+  app.post('/admin/markdown/presentationSetting', loginRequired() , adminRequired , csrf, form.admin.markdownPresentation, admin.markdown.presentationSetting);
 
   // markdown admin
-  app.get('/admin/customize'                , loginRequired(crowi, app) , middleware.adminRequired() , admin.customize.index);
-  app.post('/_api/admin/customize/css'      , loginRequired(crowi, app) , middleware.adminRequired() , csrf, form.admin.customcss, admin.api.customizeSetting);
-  app.post('/_api/admin/customize/script'   , loginRequired(crowi, app) , middleware.adminRequired() , csrf, form.admin.customscript, admin.api.customizeSetting);
-  app.post('/_api/admin/customize/header'   , loginRequired(crowi, app) , middleware.adminRequired() , csrf, form.admin.customheader, admin.api.customizeSetting);
-  app.post('/_api/admin/customize/theme'    , loginRequired(crowi, app) , middleware.adminRequired() , csrf, form.admin.customtheme, admin.api.customizeSetting);
-  app.post('/_api/admin/customize/title'    , loginRequired(crowi, app) , middleware.adminRequired() , csrf, form.admin.customtitle, admin.api.customizeSetting);
-  app.post('/_api/admin/customize/behavior' , loginRequired(crowi, app) , middleware.adminRequired() , csrf, form.admin.custombehavior, admin.api.customizeSetting);
-  app.post('/_api/admin/customize/layout'   , loginRequired(crowi, app) , middleware.adminRequired() , csrf, form.admin.customlayout, admin.api.customizeSetting);
-  app.post('/_api/admin/customize/features' , loginRequired(crowi, app) , middleware.adminRequired() , csrf, form.admin.customfeatures, admin.api.customizeSetting);
-  app.post('/_api/admin/customize/highlightJsStyle' , loginRequired(crowi, app) , middleware.adminRequired() , csrf, form.admin.customhighlightJsStyle, admin.api.customizeSetting);
+  app.get('/admin/customize'                , loginRequired() , adminRequired , admin.customize.index);
+  app.post('/_api/admin/customize/css'      , loginRequired() , adminRequired , csrf, form.admin.customcss, admin.api.customizeSetting);
+  app.post('/_api/admin/customize/script'   , loginRequired() , adminRequired , csrf, form.admin.customscript, admin.api.customizeSetting);
+  app.post('/_api/admin/customize/header'   , loginRequired() , adminRequired , csrf, form.admin.customheader, admin.api.customizeSetting);
+  app.post('/_api/admin/customize/theme'    , loginRequired() , adminRequired , csrf, form.admin.customtheme, admin.api.customizeSetting);
+  app.post('/_api/admin/customize/title'    , loginRequired() , adminRequired , csrf, form.admin.customtitle, admin.api.customizeSetting);
+  app.post('/_api/admin/customize/behavior' , loginRequired() , adminRequired , csrf, form.admin.custombehavior, admin.api.customizeSetting);
+  app.post('/_api/admin/customize/layout'   , loginRequired() , adminRequired , csrf, form.admin.customlayout, admin.api.customizeSetting);
+  app.post('/_api/admin/customize/features' , loginRequired() , adminRequired , csrf, form.admin.customfeatures, admin.api.customizeSetting);
+  app.post('/_api/admin/customize/highlightJsStyle' , loginRequired() , adminRequired , csrf, form.admin.customhighlightJsStyle, admin.api.customizeSetting);
 
   // search admin
-  app.get('/admin/search'              , loginRequired(crowi, app) , middleware.adminRequired() , admin.search.index);
-  app.post('/_api/admin/search/build'  , loginRequired(crowi, app) , middleware.adminRequired() , csrf, admin.api.searchBuildIndex);
+  app.get('/admin/search'              , loginRequired() , adminRequired , admin.search.index);
+  app.post('/_api/admin/search/build'  , loginRequired() , adminRequired , csrf, admin.api.searchBuildIndex);
 
   // notification admin
-  app.get('/admin/notification'              , loginRequired(crowi, app) , middleware.adminRequired() , admin.notification.index);
-  app.post('/admin/notification/slackIwhSetting', loginRequired(crowi, app) , middleware.adminRequired() , csrf, form.admin.slackIwhSetting, admin.notification.slackIwhSetting);
-  app.post('/admin/notification/slackSetting', loginRequired(crowi, app) , middleware.adminRequired() , csrf, form.admin.slackSetting, admin.notification.slackSetting);
-  app.get('/admin/notification/slackAuth'    , loginRequired(crowi, app) , middleware.adminRequired() , admin.notification.slackAuth);
-  app.get('/admin/notification/slackSetting/disconnect', loginRequired(crowi, app) , middleware.adminRequired() , admin.notification.disconnectFromSlack);
-  app.post('/_api/admin/notification.add'    , loginRequired(crowi, app) , middleware.adminRequired() , csrf, admin.api.notificationAdd);
-  app.post('/_api/admin/notification.remove' , loginRequired(crowi, app) , middleware.adminRequired() , csrf, admin.api.notificationRemove);
-  app.get('/_api/admin/users.search'         , loginRequired(crowi, app) , middleware.adminRequired() , admin.api.usersSearch);
-  app.get('/admin/global-notification/new'   , loginRequired(crowi, app) , middleware.adminRequired() , admin.globalNotification.detail);
-  app.get('/admin/global-notification/:id'   , loginRequired(crowi, app) , middleware.adminRequired() , admin.globalNotification.detail);
-  app.post('/admin/global-notification/new'  , loginRequired(crowi, app) , middleware.adminRequired() , form.admin.notificationGlobal, admin.globalNotification.create);
-  app.post('/_api/admin/global-notification/toggleIsEnabled', loginRequired(crowi, app) , middleware.adminRequired() , admin.api.toggleIsEnabledForGlobalNotification);
-  app.post('/admin/global-notification/:id/update', loginRequired(crowi, app) , middleware.adminRequired() , form.admin.notificationGlobal, admin.globalNotification.update);
-  app.post('/admin/global-notification/:id/remove', loginRequired(crowi, app) , middleware.adminRequired() , admin.globalNotification.remove);
-
-  app.get('/admin/users'                , loginRequired(crowi, app) , middleware.adminRequired() , admin.user.index);
-  app.post('/admin/user/invite'         , form.admin.userInvite ,  loginRequired(crowi, app) , middleware.adminRequired() , csrf, admin.user.invite);
-  app.post('/admin/user/:id/makeAdmin'  , loginRequired(crowi, app) , middleware.adminRequired() , csrf, admin.user.makeAdmin);
-  app.post('/admin/user/:id/removeFromAdmin', loginRequired(crowi, app) , middleware.adminRequired() , admin.user.removeFromAdmin);
-  app.post('/admin/user/:id/activate'   , loginRequired(crowi, app) , middleware.adminRequired() , csrf, admin.user.activate);
-  app.post('/admin/user/:id/suspend'    , loginRequired(crowi, app) , middleware.adminRequired() , csrf, admin.user.suspend);
-  app.post('/admin/user/:id/remove'     , loginRequired(crowi, app) , middleware.adminRequired() , csrf, admin.user.remove);
-  app.post('/admin/user/:id/removeCompletely' , loginRequired(crowi, app) , middleware.adminRequired() , csrf, admin.user.removeCompletely);
+  app.get('/admin/notification'              , loginRequired() , adminRequired , admin.notification.index);
+  app.post('/admin/notification/slackIwhSetting', loginRequired() , adminRequired , csrf, form.admin.slackIwhSetting, admin.notification.slackIwhSetting);
+  app.post('/admin/notification/slackSetting', loginRequired() , adminRequired , csrf, form.admin.slackSetting, admin.notification.slackSetting);
+  app.get('/admin/notification/slackAuth'    , loginRequired() , adminRequired , admin.notification.slackAuth);
+  app.get('/admin/notification/slackSetting/disconnect', loginRequired() , adminRequired , admin.notification.disconnectFromSlack);
+  app.post('/_api/admin/notification.add'    , loginRequired() , adminRequired , csrf, admin.api.notificationAdd);
+  app.post('/_api/admin/notification.remove' , loginRequired() , adminRequired , csrf, admin.api.notificationRemove);
+  app.get('/_api/admin/users.search'         , loginRequired() , adminRequired , admin.api.usersSearch);
+  app.get('/admin/global-notification/new'   , loginRequired() , adminRequired , admin.globalNotification.detail);
+  app.get('/admin/global-notification/:id'   , loginRequired() , adminRequired , admin.globalNotification.detail);
+  app.post('/admin/global-notification/new'  , loginRequired() , adminRequired , form.admin.notificationGlobal, admin.globalNotification.create);
+  app.post('/_api/admin/global-notification/toggleIsEnabled', loginRequired() , adminRequired , admin.api.toggleIsEnabledForGlobalNotification);
+  app.post('/admin/global-notification/:id/update', loginRequired() , adminRequired , form.admin.notificationGlobal, admin.globalNotification.update);
+  app.post('/admin/global-notification/:id/remove', loginRequired() , adminRequired , admin.globalNotification.remove);
+
+  app.get('/admin/users'                , loginRequired() , adminRequired , admin.user.index);
+  app.post('/admin/user/invite'         , form.admin.userInvite ,  loginRequired() , adminRequired , csrf, admin.user.invite);
+  app.post('/admin/user/:id/makeAdmin'  , loginRequired() , adminRequired , csrf, admin.user.makeAdmin);
+  app.post('/admin/user/:id/removeFromAdmin', loginRequired() , adminRequired , admin.user.removeFromAdmin);
+  app.post('/admin/user/:id/activate'   , loginRequired() , adminRequired , csrf, admin.user.activate);
+  app.post('/admin/user/:id/suspend'    , loginRequired() , adminRequired , csrf, admin.user.suspend);
+  app.post('/admin/user/:id/remove'     , loginRequired() , adminRequired , csrf, admin.user.remove);
+  app.post('/admin/user/:id/removeCompletely' , loginRequired() , adminRequired , csrf, admin.user.removeCompletely);
   // new route patterns from here:
-  app.post('/_api/admin/users.resetPassword'  , loginRequired(crowi, app) , middleware.adminRequired() , csrf, admin.user.resetPassword);
+  app.post('/_api/admin/users.resetPassword'  , loginRequired() , adminRequired , csrf, admin.user.resetPassword);
 
-  app.get('/admin/users/external-accounts'               , loginRequired(crowi, app) , middleware.adminRequired() , admin.externalAccount.index);
-  app.post('/admin/users/external-accounts/:id/remove'   , loginRequired(crowi, app) , middleware.adminRequired() , admin.externalAccount.remove);
+  app.get('/admin/users/external-accounts'               , loginRequired() , adminRequired , admin.externalAccount.index);
+  app.post('/admin/users/external-accounts/:id/remove'   , loginRequired() , adminRequired , admin.externalAccount.remove);
 
   // user-groups admin
-  app.get('/admin/user-groups'             , loginRequired(crowi, app), middleware.adminRequired(), admin.userGroup.index);
-  app.get('/admin/user-group-detail/:id'          , loginRequired(crowi, app), middleware.adminRequired(), admin.userGroup.detail);
-  app.post('/admin/user-group/create'      , form.admin.userGroupCreate, loginRequired(crowi, app), middleware.adminRequired(), csrf, admin.userGroup.create);
-  app.post('/admin/user-group/:userGroupId/update', loginRequired(crowi, app), middleware.adminRequired(), csrf, admin.userGroup.update);
-  app.post('/admin/user-group.remove' , loginRequired(crowi, app), middleware.adminRequired(), csrf, admin.userGroup.removeCompletely);
-  app.get('/_api/admin/user-groups', loginRequired(crowi, app), middleware.adminRequired(), admin.api.userGroups);
+  app.get('/admin/user-groups'             , loginRequired(), adminRequired, admin.userGroup.index);
+  app.get('/admin/user-group-detail/:id'          , loginRequired(), adminRequired, admin.userGroup.detail);
+  app.post('/admin/user-group/create'      , form.admin.userGroupCreate, loginRequired(), adminRequired, csrf, admin.userGroup.create);
+  app.post('/admin/user-group/:userGroupId/update', loginRequired(), adminRequired, csrf, admin.userGroup.update);
+  app.post('/admin/user-group.remove' , loginRequired(), adminRequired, csrf, admin.userGroup.removeCompletely);
+  app.get('/_api/admin/user-groups', loginRequired(), adminRequired, admin.api.userGroups);
 
   // user-group-relations admin
-  app.post('/admin/user-group-relation/create', loginRequired(crowi, app), middleware.adminRequired(), csrf, admin.userGroupRelation.create);
-  app.post('/admin/user-group-relation/:id/remove-relation/:relationId', loginRequired(crowi, app), middleware.adminRequired(), csrf, admin.userGroupRelation.remove);
+  app.post('/admin/user-group-relation/create', loginRequired(), adminRequired, csrf, admin.userGroupRelation.create);
+  app.post('/admin/user-group-relation/:id/remove-relation/:relationId', loginRequired(), adminRequired, csrf, admin.userGroupRelation.remove);
 
   // importer management for admin
-  app.get('/admin/importer'                , loginRequired(crowi, app) , middleware.adminRequired() , admin.importer.index);
-  app.post('/_api/admin/settings/importerEsa' , loginRequired(crowi, app) , middleware.adminRequired() , csrf , form.admin.importerEsa , admin.api.importerSettingEsa);
-  app.post('/_api/admin/settings/importerQiita' , loginRequired(crowi, app) , middleware.adminRequired() , csrf , form.admin.importerQiita , admin.api.importerSettingQiita);
-  app.post('/_api/admin/import/esa'        , loginRequired(crowi, app) , middleware.adminRequired() , admin.api.importDataFromEsa);
-  app.post('/_api/admin/import/testEsaAPI' , loginRequired(crowi, app) , middleware.adminRequired() , csrf , form.admin.importerEsa , admin.api.testEsaAPI);
-  app.post('/_api/admin/import/qiita'        , loginRequired(crowi, app) , middleware.adminRequired() , admin.api.importDataFromQiita);
-  app.post('/_api/admin/import/testQiitaAPI' , loginRequired(crowi, app) , middleware.adminRequired() , csrf , form.admin.importerQiita , admin.api.testQiitaAPI);
-
-  app.get('/me'                       , loginRequired(crowi, app) , me.index);
-  app.get('/me/password'              , loginRequired(crowi, app) , me.password);
-  app.get('/me/apiToken'              , loginRequired(crowi, app) , me.apiToken);
-  app.post('/me'                      , loginRequired(crowi, app) , csrf , form.me.user , me.index);
+  app.get('/admin/importer'                , loginRequired() , adminRequired , admin.importer.index);
+  app.post('/_api/admin/settings/importerEsa' , loginRequired() , adminRequired , csrf , form.admin.importerEsa , admin.api.importerSettingEsa);
+  app.post('/_api/admin/settings/importerQiita' , loginRequired() , adminRequired , csrf , form.admin.importerQiita , admin.api.importerSettingQiita);
+  app.post('/_api/admin/import/esa'        , loginRequired() , adminRequired , admin.api.importDataFromEsa);
+  app.post('/_api/admin/import/testEsaAPI' , loginRequired() , adminRequired , csrf , form.admin.importerEsa , admin.api.testEsaAPI);
+  app.post('/_api/admin/import/qiita'        , loginRequired() , adminRequired , admin.api.importDataFromQiita);
+  app.post('/_api/admin/import/testQiitaAPI' , loginRequired() , adminRequired , csrf , form.admin.importerQiita , admin.api.testQiitaAPI);
+
+  app.get('/me'                       , loginRequired() , me.index);
+  app.get('/me/password'              , loginRequired() , me.password);
+  app.get('/me/apiToken'              , loginRequired() , me.apiToken);
+  app.post('/me'                      , loginRequired() , csrf , form.me.user , me.index);
   // external-accounts
-  if (Config.isEnabledPassport(config)) {
-    app.get('/me/external-accounts'                         , loginRequired(crowi, app) , me.externalAccounts.list);
-    app.post('/me/external-accounts/disassociate'           , loginRequired(crowi, app) , me.externalAccounts.disassociate);
-    app.post('/me/external-accounts/associateLdap'          , loginRequired(crowi, app) , form.login , me.externalAccounts.associateLdap);
-  }
-  app.post('/me/password'             , form.me.password          , loginRequired(crowi, app) , me.password);
-  app.post('/me/imagetype'            , form.me.imagetype         , loginRequired(crowi, app) , me.imagetype);
-  app.post('/me/apiToken'             , form.me.apiToken          , loginRequired(crowi, app) , me.apiToken);
-  app.post('/me/auth/google'          , loginRequired(crowi, app) , me.authGoogle);
-  app.get('/me/auth/google/callback' , loginRequired(crowi, app) , me.authGoogleCallback);
-
-  app.get('/:id([0-9a-z]{24})'       , loginRequired(crowi, app, false) , page.redirector);
-  app.get('/_r/:id([0-9a-z]{24})'    , loginRequired(crowi, app, false) , page.redirector); // alias
-  app.get('/attachment/:pageId/:fileName'  , loginRequired(crowi, app, false), attachment.api.obsoletedGetForMongoDB); // DEPRECATED: remains for backward compatibility for v3.3.x or below
-  app.get('/attachment/:id([0-9a-z]{24})'  , loginRequired(crowi, app, false), attachment.api.get);
-  app.get('/download/:id([0-9a-z]{24})'    , loginRequired(crowi, app, false), attachment.api.download);
-
-  app.get('/_search'                 , loginRequired(crowi, app, false) , search.searchPage);
-  app.get('/_api/search'             , accessTokenParser , loginRequired(crowi, app, false) , search.api.search);
+  app.get('/me/external-accounts'                         , loginRequired() , me.externalAccounts.list);
+  app.post('/me/external-accounts/disassociate'           , loginRequired() , me.externalAccounts.disassociate);
+  app.post('/me/external-accounts/associateLdap'          , loginRequired() , form.login , me.externalAccounts.associateLdap);
+
+  app.post('/me/password'             , form.me.password          , loginRequired() , me.password);
+  app.post('/me/imagetype'            , form.me.imagetype         , loginRequired() , me.imagetype);
+  app.post('/me/apiToken'             , form.me.apiToken          , loginRequired() , me.apiToken);
+
+  app.get('/:id([0-9a-z]{24})'       , loginRequired(false) , page.redirector);
+  app.get('/_r/:id([0-9a-z]{24})'    , loginRequired(false) , page.redirector); // alias
+  app.get('/attachment/:pageId/:fileName'  , loginRequired(false), attachment.api.obsoletedGetForMongoDB); // DEPRECATED: remains for backward compatibility for v3.3.x or below
+  app.get('/attachment/:id([0-9a-z]{24})'  , loginRequired(false), attachment.api.get);
+  app.get('/download/:id([0-9a-z]{24})'    , loginRequired(false), attachment.api.download);
+
+  app.get('/_search'                 , loginRequired(false) , search.searchPage);
+  app.get('/_api/search'             , accessTokenParser , loginRequired(false) , search.api.search);
 
   app.get('/_api/check_username'           , user.api.checkUsername);
-  app.get('/_api/me/user-group-relations'  , accessTokenParser , loginRequired(crowi, app) , me.api.userGroupRelations);
-  app.get('/_api/user/bookmarks'           , loginRequired(crowi, app, false) , user.api.bookmarks);
+  app.get('/_api/me/user-group-relations'  , accessTokenParser , loginRequired() , me.api.userGroupRelations);
+  app.get('/_api/user/bookmarks'           , loginRequired(false) , user.api.bookmarks);
 
   // HTTP RPC Styled API (に徐々に移行していいこうと思う)
-  app.get('/_api/users.list'          , accessTokenParser , loginRequired(crowi, app, false) , user.api.list);
-  app.get('/_api/pages.list'          , accessTokenParser , loginRequired(crowi, app, false) , page.api.list);
-  app.get('/_api/pages.recentCreated' , accessTokenParser , loginRequired(crowi, app, false) , page.api.recentCreated);
-  app.post('/_api/pages.create'       , accessTokenParser , loginRequired(crowi, app) , csrf, page.api.create);
-  app.post('/_api/pages.update'       , accessTokenParser , loginRequired(crowi, app) , csrf, page.api.update);
-  app.get('/_api/pages.get'           , accessTokenParser , loginRequired(crowi, app, false) , page.api.get);
-  app.get('/_api/pages.exist'         , accessTokenParser , loginRequired(crowi, app, false) , page.api.exist);
-  app.get('/_api/pages.updatePost', accessTokenParser, loginRequired(crowi, app, false), page.api.getUpdatePost);
-  app.get('/_api/pages.getPageTag'    , accessTokenParser , loginRequired(crowi, app, false) , page.api.getPageTag);
+  app.get('/_api/users.list'          , accessTokenParser , loginRequired(false) , user.api.list);
+  app.get('/_api/pages.list'          , accessTokenParser , loginRequired(false) , page.api.list);
+  app.get('/_api/pages.recentCreated' , accessTokenParser , loginRequired(false) , page.api.recentCreated);
+  app.post('/_api/pages.create'       , accessTokenParser , loginRequired() , csrf, page.api.create);
+  app.post('/_api/pages.update'       , accessTokenParser , loginRequired() , csrf, page.api.update);
+  app.get('/_api/pages.get'           , accessTokenParser , loginRequired(false) , page.api.get);
+  app.get('/_api/pages.exist'         , accessTokenParser , loginRequired(false) , page.api.exist);
+  app.get('/_api/pages.updatePost'    , accessTokenParser, loginRequired(false), page.api.getUpdatePost);
+  app.get('/_api/pages.getPageTag'    , accessTokenParser , loginRequired(false) , page.api.getPageTag);
   // allow posting to guests because the client doesn't know whether the user logged in
-  app.post('/_api/pages.seen'         , accessTokenParser , loginRequired(crowi, app, false) , page.api.seen);
-  app.post('/_api/pages.rename'       , accessTokenParser , loginRequired(crowi, app) , csrf, page.api.rename);
-  app.post('/_api/pages.remove'       , loginRequired(crowi, app) , csrf, page.api.remove); // (Avoid from API Token)
-  app.post('/_api/pages.revertRemove' , loginRequired(crowi, app) , csrf, page.api.revertRemove); // (Avoid from API Token)
-  app.post('/_api/pages.unlink'       , loginRequired(crowi, app) , csrf, page.api.unlink); // (Avoid from API Token)
-  app.post('/_api/pages.duplicate', accessTokenParser, loginRequired(crowi, app), csrf, page.api.duplicate);
-  app.get('/tags'                     , loginRequired(crowi, app, false), tag.showPage);
-  app.get('/_api/tags.list'           , accessTokenParser, loginRequired(crowi, app, false), tag.api.list);
-  app.get('/_api/tags.search'         , accessTokenParser, loginRequired(crowi, app, false), tag.api.search);
-  app.post('/_api/tags.update'         , accessTokenParser, loginRequired(crowi, app, false), tag.api.update);
-  app.get('/_api/comments.get'        , accessTokenParser , loginRequired(crowi, app, false) , comment.api.get);
-  app.post('/_api/comments.add'       , comment.api.validators.add(), accessTokenParser , loginRequired(crowi, app) , csrf, comment.api.add);
-  app.post('/_api/comments.remove'    , accessTokenParser , loginRequired(crowi, app) , csrf, comment.api.remove);
-  app.get('/_api/bookmarks.get'      , accessTokenParser , loginRequired(crowi, app, false) , bookmark.api.get);
-  app.post('/_api/bookmarks.add'      , accessTokenParser , loginRequired(crowi, app) , csrf, bookmark.api.add);
-  app.post('/_api/bookmarks.remove'   , accessTokenParser , loginRequired(crowi, app) , csrf, bookmark.api.remove);
-  app.post('/_api/likes.add'          , accessTokenParser , loginRequired(crowi, app) , csrf, page.api.like);
-  app.post('/_api/likes.remove'       , accessTokenParser , loginRequired(crowi, app) , csrf, page.api.unlike);
-  app.get('/_api/attachments.list'   , accessTokenParser , loginRequired(crowi, app, false) , attachment.api.list);
-  app.post('/_api/attachments.add'                  , uploads.single('file'), autoReap, accessTokenParser, loginRequired(crowi, app) ,csrf, attachment.api.add);
-  app.post('/_api/attachments.removeProfileImage', accessTokenParser, loginRequired(crowi, app), csrf, attachment.api.removeProfileImage);
-  app.post('/_api/attachments.uploadProfileImage'   , uploads.single('file'), autoReap, accessTokenParser, loginRequired(crowi, app) ,csrf, attachment.api.uploadProfileImage);
-  app.post('/_api/attachments.remove' , accessTokenParser , loginRequired(crowi, app) , csrf, attachment.api.remove);
-  app.get('/_api/attachments.limit'   , accessTokenParser , loginRequired(crowi, app) , attachment.api.limit);
-
-  app.get('/_api/revisions.get'      , accessTokenParser , loginRequired(crowi, app, false) , revision.api.get);
-  app.get('/_api/revisions.ids'      , accessTokenParser , loginRequired(crowi, app, false) , revision.api.ids);
-  app.get('/_api/revisions.list'     , accessTokenParser , loginRequired(crowi, app, false) , revision.api.list);
-
-  app.get('/trash$'                  , loginRequired(crowi, app, false) , page.trashPageShowWrapper);
-  app.get('/trash/$'                 , loginRequired(crowi, app, false) , page.trashPageListShowWrapper);
-  app.get('/trash/*/$'               , loginRequired(crowi, app, false) , page.deletedPageListShowWrapper);
-
-  app.get('/_hackmd/load-agent'        , hackmd.loadAgent);
-  app.get('/_hackmd/load-styles'       , hackmd.loadStyles);
-  app.post('/_api/hackmd.integrate'    , accessTokenParser , loginRequired(crowi, app) , csrf, hackmd.validateForApi, hackmd.integrate);
-  app.post('/_api/hackmd.saveOnHackmd' , accessTokenParser , loginRequired(crowi, app) , csrf, hackmd.validateForApi, hackmd.saveOnHackmd);
+  app.post('/_api/pages.seen'         , accessTokenParser , loginRequired(false) , page.api.seen);
+  app.post('/_api/pages.rename'       , accessTokenParser , loginRequired() , csrf, page.api.rename);
+  app.post('/_api/pages.remove'       , loginRequired() , csrf, page.api.remove); // (Avoid from API Token)
+  app.post('/_api/pages.revertRemove' , loginRequired() , csrf, page.api.revertRemove); // (Avoid from API Token)
+  app.post('/_api/pages.unlink'       , loginRequired() , csrf, page.api.unlink); // (Avoid from API Token)
+  app.post('/_api/pages.duplicate'    , accessTokenParser, loginRequired(), csrf, page.api.duplicate);
+  app.get('/tags'                     , loginRequired(false), tag.showPage);
+  app.get('/_api/tags.list'           , accessTokenParser, loginRequired(false), tag.api.list);
+  app.get('/_api/tags.search'         , accessTokenParser, loginRequired(false), tag.api.search);
+  app.post('/_api/tags.update'        , accessTokenParser, loginRequired(false), tag.api.update);
+  app.get('/_api/comments.get'        , accessTokenParser , loginRequired(false) , comment.api.get);
+  app.post('/_api/comments.add'       , comment.api.validators.add(), accessTokenParser , loginRequired() , csrf, comment.api.add);
+  app.post('/_api/comments.remove'    , accessTokenParser , loginRequired() , csrf, comment.api.remove);
+  app.get('/_api/bookmarks.get'       , accessTokenParser , loginRequired(false) , bookmark.api.get);
+  app.post('/_api/bookmarks.add'      , accessTokenParser , loginRequired() , csrf, bookmark.api.add);
+  app.post('/_api/bookmarks.remove'   , accessTokenParser , loginRequired() , csrf, bookmark.api.remove);
+  app.post('/_api/likes.add'          , accessTokenParser , loginRequired() , csrf, page.api.like);
+  app.post('/_api/likes.remove'       , accessTokenParser , loginRequired() , csrf, page.api.unlike);
+  app.get('/_api/attachments.list'    , accessTokenParser , loginRequired(false) , attachment.api.list);
+  app.post('/_api/attachments.add'                  , uploads.single('file'), autoReap, accessTokenParser, loginRequired() ,csrf, attachment.api.add);
+  app.post('/_api/attachments.uploadProfileImage'   , uploads.single('file'), autoReap, accessTokenParser, loginRequired() ,csrf, attachment.api.uploadProfileImage);
+  app.post('/_api/attachments.remove'               , accessTokenParser , loginRequired() , csrf, attachment.api.remove);
+  app.post('/_api/attachments.removeProfileImage'   , accessTokenParser , loginRequired() , csrf, attachment.api.removeProfileImage);
+  app.get('/_api/attachments.limit'   , accessTokenParser , loginRequired(), attachment.api.limit);
+
+  app.get('/_api/revisions.get'       , accessTokenParser , loginRequired(false) , revision.api.get);
+  app.get('/_api/revisions.ids'       , accessTokenParser , loginRequired(false) , revision.api.ids);
+  app.get('/_api/revisions.list'      , accessTokenParser , loginRequired(false) , revision.api.list);
+
+  app.get('/trash$'                   , loginRequired(false) , page.trashPageShowWrapper);
+  app.get('/trash/$'                  , loginRequired(false) , page.trashPageListShowWrapper);
+  app.get('/trash/*/$'                , loginRequired(false) , page.deletedPageListShowWrapper);
+
+  app.get('/_hackmd/load-agent'          , hackmd.loadAgent);
+  app.get('/_hackmd/load-styles'         , hackmd.loadStyles);
+  app.post('/_api/hackmd.integrate'      , accessTokenParser , loginRequired() , csrf, hackmd.validateForApi, hackmd.integrate);
+  app.post('/_api/hackmd.saveOnHackmd'   , accessTokenParser , loginRequired() , csrf, hackmd.validateForApi, hackmd.saveOnHackmd);
 
   // API v3
   app.use('/api-docs', require('./apiv3/docs')(crowi));
   app.use('/_api/v3', require('./apiv3')(crowi));
 
-  app.get('/*/$'                   , loginRequired(crowi, app, false) , page.showPageWithEndOfSlash, page.notFound);
-  app.get('/*'                     , loginRequired(crowi, app, false) , page.showPage, page.notFound);
+  app.get('/*/$'                   , loginRequired(false) , page.showPageWithEndOfSlash, page.notFound);
+  app.get('/*'                     , loginRequired(false) , page.showPage, page.notFound);
 };

+ 13 - 21
src/server/routes/installer.js

@@ -2,8 +2,10 @@ module.exports = function(crowi, app) {
   const logger = require('@alias/logger')('growi:routes:installer');
   const path = require('path');
   const fs = require('graceful-fs');
+
   const models = crowi.models;
-  const Config = models.Config;
+  const { appService } = crowi;
+
   const User = models.User;
   const Page = models.Page;
 
@@ -65,6 +67,9 @@ module.exports = function(crowi, app) {
     const password = registerForm.password;
     const language = registerForm['app:globalLang'] || 'en-US';
 
+    await appService.initDB(language);
+
+    // create first admin user
     let adminUser;
     try {
       adminUser = await User.createUser(name, username, email, password, language);
@@ -74,29 +79,16 @@ module.exports = function(crowi, app) {
       req.form.errors.push(`管理ユーザーの作成に失敗しました。${err.message}`);
       return res.render('installer');
     }
+    // create initial pages
+    await createInitialPages(adminUser, language);
 
-    Config.applicationInstall((err, configs) => {
-      if (err) {
-        logger.error(err);
-        return;
-      }
-
-      // save the globalLang config, and update the config cache
-      Config.updateNamespaceByArray('crowi', { 'app:globalLang': language }, (err, config) => {
-        Config.updateConfigCache('crowi', config);
-      });
-
-      // login with passport
-      req.logIn(adminUser, (err) => {
-        if (err) { return next() }
+    // login with passport
+    req.logIn(adminUser, (err) => {
+      if (err) { return next() }
 
-        req.flash('successMessage', 'GROWI のインストールが完了しました!はじめに、このページで各種設定を確認してください。');
-        return res.redirect('/admin/app');
-      });
+      req.flash('successMessage', 'GROWI のインストールが完了しました!はじめに、このページで各種設定を確認してください。');
+      return res.redirect('/admin/app');
     });
-
-    // create initial pages
-    await createInitialPages(adminUser, language);
   };
 
   return actions;

+ 12 - 124
src/server/routes/login.js

@@ -8,16 +8,12 @@ module.exports = function(crowi, app) {
   const logger = require('@alias/logger')('growi:routes:login');
   const path = require('path');
   const async = require('async');
-  const config = crowi.getConfig();
   const mailer = crowi.getMailer();
   const User = crowi.model('User');
-  const Config = crowi.model('Config');
+  const { configManager, appService, aclService } = crowi;
 
   const actions = {};
 
-  const clearGoogleSession = function(req) {
-    req.session.googleAuthCode = req.session.googleId = req.session.googleEmail = req.session.googleName = req.session.googleImage = null;
-  };
   const loginSuccess = function(req, res, userData) {
     req.user = req.session.user = userData;
 
@@ -32,8 +28,6 @@ module.exports = function(crowi, app) {
       return res.redirect('/me/password');
     }
 
-    clearGoogleSession(req);
-
     const jumpTo = req.session.jumpTo;
     if (jumpTo) {
       req.session.jumpTo = null;
@@ -60,16 +54,6 @@ module.exports = function(crowi, app) {
     return res.redirect('/login');
   };
 
-  actions.googleCallback = function(req, res) {
-    const nextAction = req.session.googleCallbackAction || '/login';
-    debug('googleCallback.nextAction', nextAction);
-    req.session.googleAuthCode = req.query.code || '';
-    debug('google auth code', req.query.code);
-
-
-    return res.redirect(nextAction);
-  };
-
   actions.error = function(req, res) {
     const reason = req.params.reason;
 
@@ -114,43 +98,7 @@ module.exports = function(crowi, app) {
     }
   };
 
-  actions.loginGoogle = function(req, res) {
-    const googleAuth = require('../util/googleAuth')(crowi);
-    const code = req.session.googleAuthCode || null;
-
-    if (!code) {
-      googleAuth.createAuthUrl(req, (err, redirectUrl) => {
-        if (err) {
-          // TODO
-        }
-
-        req.session.googleCallbackAction = '/login/google';
-        return res.redirect(redirectUrl);
-      });
-    }
-    else {
-      googleAuth.handleCallback(req, (err, tokenInfo) => {
-        debug('handleCallback', err, tokenInfo);
-        if (err) {
-          return loginFailure(req, res);
-        }
-
-        const googleId = tokenInfo.user_id;
-        User.findUserByGoogleId(googleId, (err, userData) => {
-          debug('findUserByGoogleId', err, userData);
-          if (!userData) {
-            clearGoogleSession(req);
-            return loginFailure(req, res);
-          }
-          return loginSuccess(req, res, userData);
-        });
-      });
-    }
-  };
-
   actions.register = function(req, res) {
-    const googleAuth = require('../util/googleAuth')(crowi);
-
     // redirect to '/' if both of these are true:
     //  1. user has logged in
     //  2. req.user is not username/email string (which is set by basic-auth-connect)
@@ -159,7 +107,7 @@ module.exports = function(crowi, app) {
     }
 
     // config で closed ならさよなら
-    if (config.crowi['security:registrationMode'] == Config.SECURITY_REGISTRATION_MODE_CLOSED) {
+    if (configManager.getConfig('crowi', 'security:registrationMode') == aclService.labels.SECURITY_REGISTRATION_MODE_CLOSED) {
       return res.redirect('/');
     }
 
@@ -170,8 +118,6 @@ module.exports = function(crowi, app) {
       const username = registerForm.username;
       const email = registerForm.email;
       const password = registerForm.password;
-      var googleId = registerForm.googleId || null;
-      var googleImage = registerForm.googleImage || null;
 
       // email と username の unique チェックする
       User.isRegisterable(email, username, (isRegisterable, errOn) => {
@@ -208,8 +154,8 @@ module.exports = function(crowi, app) {
 
 
           // 作成後、承認が必要なモードなら、管理者に通知する
-          const appTitle = Config.appTitle(config);
-          if (config.crowi['security:registrationMode'] === Config.SECURITY_REGISTRATION_MODE_RESTRICTED) {
+          const appTitle = appService.getAppTitle();
+          if (configManager.getConfig('crowi', 'security:registrationMode') === aclService.labels.SECURITY_REGISTRATION_MODE_RESTRICTED) {
             // TODO send mail
             User.findAdmins((err, admins) => {
               async.each(
@@ -222,7 +168,7 @@ module.exports = function(crowi, app) {
                     vars: {
                       createdUser: userData,
                       adminUser,
-                      url: crowi.configManager.getSiteUrl(),
+                      url: appService.getSiteUrl(),
                       appTitle,
                     },
                   },
@@ -238,81 +184,23 @@ module.exports = function(crowi, app) {
             });
           }
 
-          if (googleId) {
-            userData.updateGoogleId(googleId, (err, userData) => {
-              if (err) { // TODO
-              }
-              return loginSuccess(req, res, userData);
-            });
-          }
-          else {
-            // add a flash message to inform the user that processing was successful -- 2017.09.23 Yuki Takei
-            // cz. loginSuccess method doesn't work on it's own when using passport
-            //      because `req.login()` prepared by passport is not called.
-            req.flash('successMessage', `The user '${userData.username}' is successfully created.`);
 
-            return loginSuccess(req, res, userData);
-          }
+          // add a flash message to inform the user that processing was successful -- 2017.09.23 Yuki Takei
+          // cz. loginSuccess method doesn't work on it's own when using passport
+          //      because `req.login()` prepared by passport is not called.
+          req.flash('successMessage', `The user '${userData.username}' is successfully created.`);
+
+          return loginSuccess(req, res, userData);
         });
       });
     }
     else { // method GET of form is not valid
       debug('session is', req.session);
       const isRegistering = true;
-      // google callback を受ける可能性もある
-      const code = req.session.googleAuthCode || null;
-      var googleId = req.session.googleId || null;
-      let googleEmail = req.session.googleEmail || null;
-      let googleName = req.session.googleName || null;
-      var googleImage = req.session.googleImage || null;
-
-      debug('register. if code', code);
-      // callback 経由で reigster にアクセスしてきた時最初だけこの if に入る
-      // code から email などを取得したらそれを session にいれて code は消去
-      if (code) {
-        googleAuth.handleCallback(req, (err, tokenInfo) => {
-          debug('tokenInfo on register GET', tokenInfo);
-          req.session.googleAuthCode = null;
-
-          if (err) {
-            req.flash('registerWarningMessage', 'Error on connectiong Google');
-            return res.redirect('/login?register=1'); // TODO Handling
-          }
-
-          req.session.googleId = googleId = tokenInfo.user_id;
-          req.session.googleEmail = googleEmail = tokenInfo.email;
-          req.session.googleName = googleName = tokenInfo.name;
-          req.session.googleImage = googleImage = tokenInfo.picture;
-
-          if (!User.isEmailValid(googleEmail)) {
-            req.flash('registerWarningMessage', 'このメールアドレスのGoogleアカウントはコネクトできません。');
-            return res.redirect('/login?register=1');
-          }
-          return res.render('login', {
-            isRegistering, googleId, googleEmail, googleName, googleImage,
-          });
-        });
-      }
-      else {
-        return res.render('login', {
-          isRegistering, googleId, googleEmail, googleName, googleImage,
-        });
-      }
+      return res.render('login', { isRegistering });
     }
   };
 
-  actions.registerGoogle = function(req, res) {
-    const googleAuth = require('../util/googleAuth')(crowi);
-    googleAuth.createAuthUrl(req, (err, redirectUrl) => {
-      if (err) {
-        // TODO
-      }
-
-      req.session.googleCallbackAction = '/register';
-      return res.redirect(redirectUrl);
-    });
-  };
-
   actions.invited = async function(req, res) {
     if (!req.user) {
       return res.redirect('/login');

+ 0 - 67
src/server/routes/me.js

@@ -309,72 +309,5 @@ module.exports = function(crowi, app) {
     });
   };
 
-  actions.authGoogle = function(req, res) {
-    const googleAuth = require('../util/googleAuth')(crowi);
-
-    const userData = req.user;
-
-    const toDisconnect = !!req.body.disconnectGoogle;
-    const toConnect = !!req.body.connectGoogle;
-    if (toDisconnect) {
-      userData.deleteGoogleId((err, userData) => {
-        req.flash('successMessage', 'Disconnected from Google account');
-
-        return res.redirect('/me');
-      });
-    }
-    else if (toConnect) {
-      googleAuth.createAuthUrl(req, (err, redirectUrl) => {
-        if (err) {
-          // TODO
-        }
-
-        req.session.googleCallbackAction = '/me/auth/google/callback';
-        return res.redirect(redirectUrl);
-      });
-    }
-    else {
-      return res.redirect('/me');
-    }
-  };
-
-  actions.authGoogleCallback = function(req, res) {
-    const googleAuth = require('../util/googleAuth')(crowi);
-    const userData = req.user;
-
-    googleAuth.handleCallback(req, (err, tokenInfo) => {
-      if (err) {
-        req.flash('warningMessage.auth.google', err.message); // FIXME: show library error message directly
-        return res.redirect('/me'); // TODO Handling
-      }
-
-      const googleId = tokenInfo.user_id;
-      const googleEmail = tokenInfo.email;
-      if (!User.isEmailValid(googleEmail)) {
-        req.flash('warningMessage.auth.google', 'You can\'t connect with this  Google\'s account');
-        return res.redirect('/me');
-      }
-
-      User.findUserByGoogleId(googleId, (err, googleUser) => {
-        if (!err && googleUser) {
-          req.flash('warningMessage.auth.google', 'This Google\'s account is connected by another user');
-          return res.redirect('/me');
-        }
-
-        userData.updateGoogleId(googleId, (err, userData) => {
-          if (err) {
-            debug('Failed to updateGoogleId', err);
-            req.flash('warningMessage.auth.google', 'Failed to connect Google Account');
-            return res.redirect('/me');
-          }
-
-          // TODO if err
-          req.flash('successMessage', 'Connected with Google');
-          return res.redirect('/me');
-        });
-      });
-    });
-  };
-
   return actions;
 };

+ 18 - 15
src/server/routes/page.js

@@ -2,18 +2,21 @@
 module.exports = function(crowi, app) {
   const debug = require('debug')('growi:routes:page');
   const logger = require('@alias/logger')('growi:routes:page');
+  const swig = require('swig-templates');
+
   const pathUtils = require('growi-commons').pathUtils;
+
   const Page = crowi.model('Page');
   const User = crowi.model('User');
-  const Config = crowi.model('Config');
-  const config = crowi.getConfig();
   const Bookmark = crowi.model('Bookmark');
   const PageTagRelation = crowi.model('PageTagRelation');
   const UpdatePost = crowi.model('UpdatePost');
+
   const ApiResponse = require('../util/apiResponse');
-  const interceptorManager = crowi.getInterceptorManager();
-  const swig = require('swig-templates');
   const getToday = require('../util/getToday');
+
+  const { configManager, slackNotificationService } = crowi;
+  const interceptorManager = crowi.getInterceptorManager();
   const globalNotificationService = crowi.getGlobalNotificationService();
 
   const actions = {};
@@ -94,7 +97,7 @@ module.exports = function(crowi, app) {
         logger.error('Error occured in updating slack channels: ', err);
       });
 
-    if (Config.hasSlackConfig(config)) {
+    if (slackNotificationService.hasSlackConfig()) {
       const promises = slackChannels.split(',').map((chan) => {
         return crowi.slack.postPage(page, user, chan, updateOrCreate, previousRevision);
       });
@@ -303,9 +306,9 @@ module.exports = function(crowi, app) {
    */
   /* eslint-disable no-else-return */
   actions.showPageWithEndOfSlash = function(req, res, next) {
-    const behaviorType = Config.behaviorType(config);
+    const behaviorType = configManager.getConfig('crowi', 'customize:behavior');
 
-    if (!behaviorType || behaviorType === 'crowi') {
+    if (behaviorType === 'crowi') {
       return showPageListForCrowiBehavior(req, res, next);
     }
     else {
@@ -327,10 +330,10 @@ module.exports = function(crowi, app) {
       return showPageForPresentation(req, res, next);
     }
 
-    const behaviorType = Config.behaviorType(config);
+    const behaviorType = configManager.getConfig('crowi', 'customize:behavior');
 
     // check whether this page has portal page
-    if (!behaviorType || behaviorType === 'crowi') {
+    if (behaviorType === 'crowi') {
       const portalPagePath = pathUtils.addTrailingSlash(getPathFromRequest(req));
       const hasPortalPage = await Page.count({ path: portalPagePath }) > 0;
 
@@ -349,9 +352,9 @@ module.exports = function(crowi, app) {
    */
   /* eslint-disable no-else-return */
   actions.trashPageListShowWrapper = function(req, res) {
-    const behaviorType = Config.behaviorType(config);
+    const behaviorType = configManager.getConfig('crowi', 'customize:behavior');
 
-    if (!behaviorType || behaviorType === 'crowi') {
+    if (behaviorType === 'crowi') {
       // Crowi behavior for '/trash/*'
       return actions.deletedPageListShow(req, res);
     }
@@ -367,9 +370,9 @@ module.exports = function(crowi, app) {
    */
   /* eslint-disable no-else-return */
   actions.trashPageShowWrapper = function(req, res) {
-    const behaviorType = Config.behaviorType(config);
+    const behaviorType = configManager.getConfig('crowi', 'customize:behavior');
 
-    if (!behaviorType || behaviorType === 'crowi') {
+    if (behaviorType === 'crowi') {
       // redirect to '/trash/'
       return res.redirect('/trash/');
     }
@@ -385,9 +388,9 @@ module.exports = function(crowi, app) {
    */
   /* eslint-disable no-else-return */
   actions.deletedPageListShowWrapper = function(req, res) {
-    const behaviorType = Config.behaviorType(config);
+    const behaviorType = configManager.getConfig('crowi', 'customize:behavior');
 
-    if (!behaviorType || behaviorType === 'crowi') {
+    if (behaviorType === 'crowi') {
       // Crowi behavior for '/trash/*'
       return actions.deletedPageListShow(req, res);
     }

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

@@ -0,0 +1,69 @@
+const logger = require('@alias/logger')('growi:service:AclService'); // eslint-disable-line no-unused-vars
+
+/**
+ * the service class of AclService
+ */
+class AclService {
+
+  constructor(configManager) {
+    this.configManager = configManager;
+    this.labels = {
+      SECURITY_RESTRICT_GUEST_MODE_DENY: 'Deny',
+      SECURITY_RESTRICT_GUEST_MODE_READONLY: 'Readonly',
+      SECURITY_REGISTRATION_MODE_OPEN: 'Open',
+      SECURITY_REGISTRATION_MODE_RESTRICTED: 'Restricted',
+      SECURITY_REGISTRATION_MODE_CLOSED: 'Closed',
+    };
+  }
+
+  getIsPublicWikiOnly() {
+    const publicWikiOnly = process.env.PUBLIC_WIKI_ONLY;
+    return !!publicWikiOnly;
+  }
+
+  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 this.labels.SECURITY_RESTRICT_GUEST_MODE_READONLY === isRestrictGuestMode;
+  }
+
+  getRestrictGuestModeLabels() {
+    const labels = {};
+    labels[this.labels.SECURITY_RESTRICT_GUEST_MODE_DENY] = 'security_setting.guest_mode.deny';
+    labels[this.labels.SECURITY_RESTRICT_GUEST_MODE_READONLY] = 'security_setting.guest_mode.readonly';
+
+    return labels;
+  }
+
+  getRegistrationModeLabels() {
+    const labels = {};
+    labels[this.labels.SECURITY_REGISTRATION_MODE_OPEN] = 'security_setting.registration_mode.open';
+    labels[this.labels.SECURITY_REGISTRATION_MODE_RESTRICTED] = 'security_setting.registration_mode.restricted';
+    labels[this.labels.SECURITY_REGISTRATION_MODE_CLOSED] = 'security_setting.registration_mode.closed';
+
+    return labels;
+  }
+
+  userUpperLimit() {
+    // const limit = this.configManager.getConfig('crowi', 'USER_UPPER_LIMIT');
+    const limit = process.env.USER_UPPER_LIMIT;
+
+    if (limit) {
+      return Number(limit);
+    }
+
+    return 0;
+  }
+
+}
+
+module.exports = AclService;

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

@@ -0,0 +1,54 @@
+const logger = require('@alias/logger')('growi:service:AppService'); // eslint-disable-line no-unused-vars
+const { pathUtils } = require('growi-commons');
+
+/**
+ * the service class of AppService
+ */
+class AppService {
+
+  constructor(configManager) {
+    this.configManager = configManager;
+  }
+
+  getAppTitle() {
+    return this.configManager.getConfig('crowi', 'app:title') || 'GROWI';
+  }
+
+  /**
+   * 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 */
+
+  /**
+   * Execute only once for installing application
+   */
+  async initDB(globalLang) {
+    const initialConfig = this.configManager.configModel.getConfigsObjectForInstalling();
+    initialConfig['app:globalLang'] = globalLang;
+    await this.configManager.updateConfigsInTheSameNamespace('crowi', initialConfig);
+  }
+
+  async isDBInitialized() {
+    const appInstalled = await this.configManager.getConfigFromDB('crowi', 'app:installed');
+    return appInstalled;
+  }
+
+}
+
+module.exports = AppService;

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

@@ -218,7 +218,7 @@ class ConfigLoader {
     // merge defaults
     let mergedConfigFromDB = Object.assign({ crowi: this.configModel.getDefaultCrowiConfigsObject() }, configFromDB);
     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.
     // So, if a value loaded from the database is emtpy,

+ 86 - 28
src/server/service/config-manager.js

@@ -1,5 +1,4 @@
-const debug = require('debug')('growi:service:ConfigManager');
-const pathUtils = require('growi-commons').pathUtils;
+const logger = require('@alias/logger')('growi:service:ConfigManager');
 const ConfigLoader = require('../service/config-loader');
 
 const KEYS_FOR_SAML_USE_ONLY_ENV_OPTION = [
@@ -20,6 +19,9 @@ class ConfigManager {
     this.configModel = configModel;
     this.configLoader = new ConfigLoader(this.configModel);
     this.configObject = null;
+    this.configKeys = [];
+
+    this.getConfig = this.getConfig.bind(this);
   }
 
   /**
@@ -27,8 +29,10 @@ class ConfigManager {
    */
   async loadConfigs() {
     this.configObject = await this.configLoader.load();
+    logger.debug('ConfigManager#loadConfigs', this.configObject);
 
-    debug('ConfigManager#loadConfigs', this.configObject);
+    // cache all config keys
+    this.reloadConfigKeys();
   }
 
   /**
@@ -44,11 +48,77 @@ class ConfigManager {
    * - undefined: a specified config does not exist.
    */
   getConfig(namespace, key) {
+    let value;
+
     if (this.searchOnlyFromEnvVarConfigs('crowi', 'security:passport-saml:useOnlyEnvVarsForSomeOptions')) {
-      return this.searchInSAMLUseOnlyEnvMode(namespace, key);
+      value = this.searchInSAMLUseOnlyEnvMode(namespace, key);
+    }
+
+    value = this.defaultSearch(namespace, key);
+
+    logger.debug(key, value);
+    return value;
+  }
+
+  /**
+   * 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
+   */
+  getConfigKeys() {
+    // 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])];
+      }
     }
 
-    return this.defaultSearch(namespace, key);
+    // 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;
+  }
+
+  reloadConfigKeys() {
+    this.configKeys = this.getConfigKeys();
   }
 
   /**
@@ -69,27 +139,6 @@ class ConfigManager {
     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!]';
-    }
-  }
-  /* eslint-enable no-else-return */
-
   /**
    * update configs in the same namespace
    *
@@ -122,6 +171,7 @@ class ConfigManager {
     await this.configModel.bulkWrite(queries);
 
     await this.loadConfigs();
+    this.reloadConfigKeys();
   }
 
   /*
@@ -133,27 +183,35 @@ class ConfigManager {
    * and then from configs loaded from the environment variables
    */
   defaultSearch(namespace, key) {
+    // does not exist neither in db nor in env vars
     if (!this.configExistsInDB(namespace, key) && !this.configExistsInEnvVars(namespace, key)) {
+      logger.debug(`${namespace}.${key} does not exist neither in db nor in env vars`);
       return undefined;
     }
 
+    // only exists in db
     if (this.configExistsInDB(namespace, key) && !this.configExistsInEnvVars(namespace, key)) {
+      logger.debug(`${namespace}.${key} only exists in db`);
       return this.configObject.fromDB[namespace][key];
     }
 
+    // only exists env vars
     if (!this.configExistsInDB(namespace, key) && this.configExistsInEnvVars(namespace, key)) {
+      logger.debug(`${namespace}.${key} only exists in env vars`);
       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)) {
-      /* eslint-disable no-else-return */
       if (this.configObject.fromDB[namespace][key] !== null) {
+        logger.debug(`${namespace}.${key} exists both in db and in env vars. loaded from db`);
         return this.configObject.fromDB[namespace][key];
       }
+      /* eslint-disable-next-line no-else-return */
       else {
+        logger.debug(`${namespace}.${key} exists both in db and in env vars. loaded from env vars`);
         return this.configObject.fromEnvVars[namespace][key];
       }
-      /* eslint-enable no-else-return */
     }
   }
 

+ 56 - 0
src/server/service/customize.js

@@ -0,0 +1,56 @@
+const logger = require('@alias/logger')('growi:service:CustomizeService'); // eslint-disable-line no-unused-vars
+
+/**
+ * the service class of CustomizeService
+ */
+class CustomizeService {
+
+  constructor(configManager, appService, xssService) {
+    this.configManager = configManager;
+    this.appService = appService;
+    this.xssService = xssService;
+  }
+
+  /**
+   * initialize custom css strings
+   */
+  initCustomCss() {
+    const uglifycss = require('uglifycss');
+
+    const rawCss = this.configManager.getConfig('crowi', 'customize:css') || '';
+
+    // uglify and store
+    this.customCss = uglifycss.processString(rawCss);
+  }
+
+  getCustomCss() {
+    return this.customCss;
+  }
+
+  getCustomScript() {
+    return this.configManager.getConfig('crowi', 'customize:script') || '';
+  }
+
+  initCustomTitle() {
+    let configValue = this.configManager.getConfig('crowi', 'customize:title');
+
+    if (configValue == null || configValue.trim().length === 0) {
+      configValue = '{{page}} - {{sitename}}';
+    }
+
+    this.customTitleTemplate = configValue;
+  }
+
+  generateCustomTitle(page) {
+    // replace
+    const customTitle = this.customTitleTemplate
+      .replace('{{sitename}}', this.appService.getAppTitle())
+      .replace('{{page}}', page);
+
+    return this.xssService.process(customTitle);
+  }
+
+
+}
+
+module.exports = CustomizeService;

+ 11 - 12
src/server/service/file-uploader/aws.js

@@ -5,24 +5,23 @@ const urljoin = require('url-join');
 const aws = require('aws-sdk');
 
 module.exports = function(crowi) {
-  const lib = {};
+  const Uploader = require('./uploader');
+  const { configManager } = crowi;
+  const lib = new Uploader(configManager);
 
   function getAwsConfig() {
-    const config = crowi.getConfig();
     return {
-      accessKeyId: config.crowi['aws:accessKeyId'],
-      secretAccessKey: config.crowi['aws:secretAccessKey'],
-      region: config.crowi['aws:region'],
-      bucket: config.crowi['aws:bucket'],
+      accessKeyId: configManager.getConfig('crowi', 'aws:accessKeyId'),
+      secretAccessKey: configManager.getConfig('crowi', 'aws:secretAccessKey'),
+      region: configManager.getConfig('crowi', 'aws:region'),
+      bucket: configManager.getConfig('crowi', 'aws:bucket'),
     };
   }
 
-  function S3Factory() {
+  function S3Factory(isUploadable) {
     const awsConfig = getAwsConfig();
-    const Config = crowi.model('Config');
-    const config = crowi.getConfig();
 
-    if (!Config.isUploadable(config)) {
+    if (!isUploadable) {
       throw new Error('AWS is not configured.');
     }
 
@@ -54,7 +53,7 @@ module.exports = function(crowi) {
   };
 
   lib.deleteFileByFilePath = async function(filePath) {
-    const s3 = S3Factory();
+    const s3 = S3Factory(this.getIsUploadable());
     const awsConfig = getAwsConfig();
 
     const params = {
@@ -68,7 +67,7 @@ module.exports = function(crowi) {
   lib.uploadFile = function(fileStream, attachment) {
     logger.debug(`File uploading: fileName=${attachment.fileName}`);
 
-    const s3 = S3Factory();
+    const s3 = S3Factory(this.getIsUploadable());
     const awsConfig = getAwsConfig();
 
     const filePath = getFilePathOnStorage(attachment);

+ 2 - 1
src/server/service/file-uploader/gridfs.js

@@ -3,7 +3,8 @@ const mongoose = require('mongoose');
 const util = require('util');
 
 module.exports = function(crowi) {
-  const lib = {};
+  const Uploader = require('./uploader');
+  const lib = new Uploader(crowi.configManager);
   const COLLECTION_NAME = 'attachmentFiles';
   const CHUNK_COLLECTION_NAME = `${COLLECTION_NAME}.chunks`;
 

+ 1 - 0
src/server/service/file-uploader/index.js

@@ -22,6 +22,7 @@ class FileUploaderFactory {
 }
 
 const factory = new FileUploaderFactory();
+
 module.exports = (crowi) => {
   return factory.getUploader(crowi);
 };

+ 2 - 1
src/server/service/file-uploader/local.js

@@ -6,7 +6,8 @@ const mkdir = require('mkdirp');
 const streamToPromise = require('stream-to-promise');
 
 module.exports = function(crowi) {
-  const lib = {};
+  const Uploader = require('./uploader');
+  const lib = new Uploader(crowi.configManager);
   const basePath = path.posix.join(crowi.publicDir, 'uploads');
 
   function getFilePathOnStorage(attachment) {

+ 2 - 2
src/server/service/file-uploader/none.js

@@ -2,8 +2,8 @@
 
 module.exports = function(crowi) {
   const debug = require('debug')('growi:service:fileUploaderNone');
-
-  const lib = {};
+  const Uploader = require('./uploader');
+  const lib = new Uploader(crowi.configManager);
 
   lib.deleteFile = function(filePath) {
     debug(`File deletion: ${filePath}`);

+ 34 - 0
src/server/service/file-uploader/uploader.js

@@ -0,0 +1,34 @@
+// file uploader virtual class
+// 各アップローダーで共通のメソッドはここで定義する
+
+class Uploader {
+
+  constructor(configManager) {
+    this.configManager = configManager;
+  }
+
+  getIsUploadable() {
+    const method = process.env.FILE_UPLOAD || 'aws';
+
+    if (method === 'aws' && (
+      !this.configManager.getConfig('crowi', 'aws:accessKeyId')
+        || !this.configManager.getConfig('crowi', 'aws:secretAccessKey')
+        || !this.configManager.getConfig('crowi', 'aws:region')
+        || !this.configManager.getConfig('crowi', 'aws:bucket'))) {
+      return false;
+    }
+
+    return method !== 'none';
+  }
+
+  getFileUploadEnabled() {
+    if (!this.getIsUploadable()) {
+      return false;
+    }
+
+    return !!this.configManager.getConfig('crowi', 'app:fileUpload');
+  }
+
+}
+
+module.exports = Uploader;

+ 1 - 3
src/server/service/global-notification.js

@@ -7,12 +7,10 @@ class GlobalNotificationService {
 
   constructor(crowi) {
     this.crowi = crowi;
-    this.config = crowi.getConfig();
     this.mailer = crowi.getMailer();
     this.GlobalNotification = crowi.model('GlobalNotificationSetting');
     this.User = crowi.model('User');
-    this.Config = crowi.model('Config');
-    this.appTitle = this.Config.appTitle(this.config);
+    this.appTitle = crowi.appService.getAppTitle();
   }
 
   notifyByMail(notification, mailOption) {

+ 0 - 22
src/server/service/notification.js

@@ -1,22 +0,0 @@
-
-
-function Notification(crowi) {
-  this.crowi = crowi;
-  this.config = crowi.getConfig();
-}
-
-Notification.prototype.hasSlackConfig = function() {
-  if (!this.config.notification.slack) {
-    return false;
-  }
-
-  // var config = ;
-};
-
-Notification.prototype.noitfyByEmail = function() {
-};
-
-Notification.prototype.noitfyByChat = function() {
-};
-
-module.exports = Notification;

+ 45 - 50
src/server/service/passport.js

@@ -154,8 +154,9 @@ class PassportService {
     }
 
     const config = this.crowi.config;
-    const Config = this.crowi.model('Config');
-    const isLdapEnabled = Config.isEnabledPassportLdap(config);
+    const { configManager } = this.crowi;
+
+    const isLdapEnabled = configManager.getConfig('crowi', 'security:passport-ldap:isEnabled');
 
     // when disabled
     if (!isLdapEnabled) {
@@ -185,8 +186,7 @@ class PassportService {
    * @memberof PassportService
    */
   getLdapAttrNameMappedToUsername() {
-    const config = this.crowi.config;
-    return config.crowi['security:passport-ldap:attrMapUsername'] || 'uid';
+    return this.crowi.configManager.getConfig('crowi', 'security:passport-ldap:attrMapUsername') || 'uid';
   }
 
   /**
@@ -196,8 +196,7 @@ class PassportService {
    * @memberof PassportService
    */
   getLdapAttrNameMappedToName() {
-    const config = this.crowi.config;
-    return config.crowi['security:passport-ldap:attrMapName'] || '';
+    return this.crowi.configManager.getConfig('crowi', 'security:passport-ldap:attrMapName') || '';
   }
 
   /**
@@ -207,8 +206,7 @@ class PassportService {
    * @memberof PassportService
    */
   getLdapAttrNameMappedToMail() {
-    const config = this.crowi.config;
-    return config.crowi['security:passport-ldap:attrMapMail'] || 'mail';
+    return this.crowi.configManager.getConfig('crowi', 'security:passport-ldap:attrMapMail') || 'mail';
   }
 
   /**
@@ -233,16 +231,17 @@ class PassportService {
    */
   getLdapConfigurationFunc(config, opts) {
     /* eslint-disable no-multi-spaces */
+    const { configManager } = this.crowi;
 
     // get configurations
-    const isUserBind          = config.crowi['security:passport-ldap:isUserBind'];
-    const serverUrl           = config.crowi['security:passport-ldap:serverUrl'];
-    const bindDN              = config.crowi['security:passport-ldap:bindDN'];
-    const bindCredentials     = config.crowi['security:passport-ldap:bindDNPassword'];
-    const searchFilter        = config.crowi['security:passport-ldap:searchFilter'] || '(uid={{username}})';
-    const groupSearchBase     = config.crowi['security:passport-ldap:groupSearchBase'];
-    const groupSearchFilter   = config.crowi['security:passport-ldap:groupSearchFilter'];
-    const groupDnProperty     = config.crowi['security:passport-ldap:groupDnProperty'] || 'uid';
+    const isUserBind          = configManager.getConfig('crowi', 'security:passport-ldap:isUserBind');
+    const serverUrl           = configManager.getConfig('crowi', 'security:passport-ldap:serverUrl');
+    const bindDN              = configManager.getConfig('crowi', 'security:passport-ldap:bindDN');
+    const bindCredentials     = configManager.getConfig('crowi', 'security:passport-ldap:bindDNPassword');
+    const searchFilter        = configManager.getConfig('crowi', 'security:passport-ldap:searchFilter') || '(uid={{username}})';
+    const groupSearchBase     = configManager.getConfig('crowi', 'security:passport-ldap:groupSearchBase');
+    const groupSearchFilter   = configManager.getConfig('crowi', 'security:passport-ldap:groupSearchFilter');
+    const groupDnProperty     = configManager.getConfig('crowi', 'security:passport-ldap:groupDnProperty') || 'uid';
     /* eslint-enable no-multi-spaces */
 
     // parse serverUrl
@@ -320,9 +319,8 @@ class PassportService {
       throw new Error('GoogleStrategy has already been set up');
     }
 
-    const config = this.crowi.config;
-    const Config = this.crowi.model('Config');
-    const isGoogleEnabled = Config.isEnabledPassportGoogle(config);
+    const { configManager } = this.crowi;
+    const isGoogleEnabled = configManager.getConfig('crowi', 'security:passport-google:isEnabled');
 
     // when disabled
     if (!isGoogleEnabled) {
@@ -333,11 +331,11 @@ class PassportService {
     passport.use(
       new GoogleStrategy(
         {
-          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,
-          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
-            : config.crowi['security:passport-google:callbackUrl'] || process.env.OAUTH_GOOGLE_CALLBACK_URI, // DEPRECATED: backward compatible with v3.2.3 and below
+          clientId: configManager.getConfig('crowi', 'security:passport-google:clientId'),
+          clientSecret: configManager.getConfig('crowi', 'security:passport-google:clientSecret'),
+          callbackURL: (this.crowi.appService.getSiteUrl() != null)
+            ? urljoin(this.crowi.appService.getSiteUrl(), '/passport/google/callback') // auto-generated with v3.2.4 and above
+            : configManager.getConfig('crowi', 'security:passport-google:callbackUrl'), // DEPRECATED: backward compatible with v3.2.3 and below
           skipUserProfile: false,
         },
         (accessToken, refreshToken, profile, done) => {
@@ -371,9 +369,8 @@ class PassportService {
       throw new Error('GitHubStrategy has already been set up');
     }
 
-    const config = this.crowi.config;
-    const Config = this.crowi.model('Config');
-    const isGitHubEnabled = Config.isEnabledPassportGitHub(config);
+    const { configManager } = this.crowi;
+    const isGitHubEnabled = configManager.getConfig('crowi', 'security:passport-github:isEnabled');
 
     // when disabled
     if (!isGitHubEnabled) {
@@ -384,11 +381,11 @@ class PassportService {
     passport.use(
       new GitHubStrategy(
         {
-          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,
-          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
-            : config.crowi['security:passport-github:callbackUrl'] || process.env.OAUTH_GITHUB_CALLBACK_URI, // DEPRECATED: backward compatible with v3.2.3 and below
+          clientID: configManager.getConfig('crowi', 'security:passport-github:clientId'),
+          clientSecret: configManager.getConfig('crowi', 'security:passport-github:clientSecret'),
+          callbackURL: (this.crowi.appService.getSiteUrl() != null)
+            ? urljoin(this.crowi.appService.getSiteUrl(), '/passport/github/callback') // auto-generated with v3.2.4 and above
+            : configManager.getConfig('crowi', 'security:passport-github:callbackUrl'), // DEPRECATED: backward compatible with v3.2.3 and below
           skipUserProfile: false,
         },
         (accessToken, refreshToken, profile, done) => {
@@ -422,9 +419,8 @@ class PassportService {
       throw new Error('TwitterStrategy has already been set up');
     }
 
-    const config = this.crowi.config;
-    const Config = this.crowi.model('Config');
-    const isTwitterEnabled = Config.isEnabledPassportTwitter(config);
+    const { configManager } = this.crowi;
+    const isTwitterEnabled = configManager.getConfig('crowi', 'security:passport-twitter:isEnabled');
 
     // when disabled
     if (!isTwitterEnabled) {
@@ -435,11 +431,11 @@ class PassportService {
     passport.use(
       new TwitterStrategy(
         {
-          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,
-          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
-            : config.crowi['security:passport-twitter:callbackUrl'] || process.env.OAUTH_TWITTER_CALLBACK_URI, // DEPRECATED: backward compatible with v3.2.3 and below
+          consumerKey: configManager.getConfig('crowi', 'security:passport-twitter:consumerKey'),
+          consumerSecret: configManager.getConfig('crowi', 'security:passport-twitter:consumerSecret'),
+          callbackURL: (this.crowi.appService.getSiteUrl() != null)
+            ? urljoin(this.crowi.appService.getSiteUrl(), '/passport/twitter/callback') // auto-generated with v3.2.4 and above
+            : configManager.getConfig('crowi', 'security:passport-twitter:callbackUrl'), // DEPRECATED: backward compatible with v3.2.3 and below
           skipUserProfile: false,
         },
         (accessToken, refreshToken, profile, done) => {
@@ -473,8 +469,7 @@ class PassportService {
       throw new Error('OidcStrategy has already been set up');
     }
 
-    const config = this.crowi.config;
-    const configManager = this.crowi.configManager;
+    const { configManager } = this.crowi;
     const isOidcEnabled = configManager.getConfig('crowi', 'security:passport-oidc:isEnabled');
 
     // when disabled
@@ -487,12 +482,12 @@ class PassportService {
     // setup client
     // extend oidc request timeouts
     OIDCIssuer.defaultHttpOptions = { timeout: 5000 };
-    const issuerHost = configManager.getConfig('crowi', 'security:passport-oidc:issuerHost') || process.env.OAUTH_OIDC_ISSUER_HOST;
-    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 issuerHost = configManager.getConfig('crowi', 'security:passport-oidc:issuerHost');
+    const clientId = configManager.getConfig('crowi', 'security:passport-oidc:clientId');
+    const clientSecret = configManager.getConfig('crowi', 'security:passport-oidc:clientSecret');
     const redirectUri = (configManager.getConfig('crowi', 'app:siteUrl') != null)
-      ? urljoin(this.crowi.configManager.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
+      ? urljoin(this.crowi.appService.getSiteUrl(), '/passport/oidc/callback')
+      : configManager.getConfig('crowi', 'security:passport-oidc:callbackUrl'); // DEPRECATED: backward compatible with v3.2.3 and below
     const oidcIssuer = await OIDCIssuer.discover(issuerHost);
     debug('Discovered issuer %s %O', oidcIssuer.issuer, oidcIssuer.metadata);
 
@@ -537,7 +532,7 @@ class PassportService {
       throw new Error('SamlStrategy has already been set up');
     }
 
-    const configManager = this.crowi.configManager;
+    const { configManager } = this.crowi;
     const isSamlEnabled = configManager.getConfig('crowi', 'security:passport-saml:isEnabled');
 
     // when disabled
@@ -550,8 +545,8 @@ class PassportService {
       new SamlStrategy(
         {
           entryPoint: configManager.getConfig('crowi', 'security:passport-saml:entryPoint'),
-          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
+          callbackUrl: (this.crowi.appService.getSiteUrl() != null)
+            ? 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
           issuer: configManager.getConfig('crowi', 'security:passport-saml:issuer'),
           cert: configManager.getConfig('crowi', 'security:passport-saml:cert'),

+ 5 - 5
src/server/service/rest-qiita-API.js

@@ -19,9 +19,9 @@ class RestQiitaAPIService {
 
   constructor(crowi) {
     this.crowi = crowi;
-    this.config = crowi.getConfig();
-    this.team = this.config.crowi['importer:qiita:team_name'];
-    this.token = this.config.crowi['importer:qiita:access_token'];
+    this.configManager = crowi.configManager;
+    this.team = this.configManager.getConfig('crowi', 'importer:qiita:team_name');
+    this.token = this.configManager.getConfig('crowi', 'importer:qiita:access_token');
     this.axios = getAxios(this.team, this.token);
   }
 
@@ -31,8 +31,8 @@ class RestQiitaAPIService {
    * @param {string} token
    */
   async reset() {
-    this.team = this.config.crowi['importer:qiita:team_name'];
-    this.token = this.config.crowi['importer:qiita:access_token'];
+    this.team = this.configManager.getConfig('crowi', 'importer:qiita:team_name');
+    this.token = this.configManager.getConfig('crowi', 'importer:qiita:access_token');
     this.axios = getAxios(this.team, this.token);
   }
 

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

@@ -0,0 +1,20 @@
+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() {
+    const hasSlackToken = !!this.configManager.getConfig('notification', 'slack:token');
+    const hasSlackIwhUrl = !!this.configManager.getConfig('notification', 'slack:incomingWebhookUrl');
+
+    return hasSlackToken || hasSlackIwhUrl;
+  }
+
+}
+
+module.exports = SlackNotificationService;

+ 23 - 0
src/server/service/user-group.js

@@ -0,0 +1,23 @@
+const logger = require('@alias/logger')('growi:service:UserGroupService'); // eslint-disable-line no-unused-vars
+
+const mongoose = require('mongoose');
+
+const UserGroupRelation = mongoose.model('UserGroupRelation');
+
+/**
+ * the service class of UserGroupService
+ */
+class UserGroupService {
+
+  constructor(configManager) {
+    this.configManager = configManager;
+  }
+
+  async init() {
+    logger.debug('removing all invalid relations');
+    return UserGroupRelation.removeAllInvalidRelations();
+  }
+
+}
+
+module.exports = UserGroupService;

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

@@ -0,0 +1,71 @@
+const logger = require('@alias/logger')('growi:service:XssSerivce'); // eslint-disable-line no-unused-vars
+
+const Xss = require('@commons/service/xss');
+const { tags, attrs } = require('@commons/service/xss/recommended-whitelist');
+
+/**
+ * the service class of XssSerivce
+ */
+class XssSerivce {
+
+  constructor(configManager) {
+    this.configManager = configManager;
+
+    this.xss = new Xss();
+  }
+
+  process(value) {
+    return this.xss.process(value);
+  }
+
+  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;

+ 0 - 70
src/server/util/googleAuth.js

@@ -1,70 +0,0 @@
-const debug = require('debug')('growi:lib:googleAuth');
-const urljoin = require('url-join');
-const { GoogleApis } = require('googleapis');
-
-/**
- * googleAuth utility
- */
-
-module.exports = function(crowi) {
-  const google = new GoogleApis();
-  const config = crowi.getConfig();
-
-  const lib = {};
-  function createOauth2Client(url) {
-    return new google.auth.OAuth2(
-      config.crowi['google:clientId'],
-      config.crowi['google:clientSecret'],
-      url,
-    );
-  }
-
-  lib.createAuthUrl = function(req, callback) {
-    const callbackUrl = urljoin(crowi.configManager.getSiteUrl(), '/google/callback');
-    const oauth2Client = createOauth2Client(callbackUrl);
-    google.options({ auth: oauth2Client });
-
-    const redirectUrl = oauth2Client.generateAuthUrl({
-      access_type: 'offline',
-      scope: ['profile', 'email'],
-    });
-
-    callback(null, redirectUrl);
-  };
-
-  lib.handleCallback = function(req, callback) {
-    const callbackUrl = urljoin(crowi.configManager.getSiteUrl(), '/google/callback');
-    const oauth2Client = createOauth2Client(callbackUrl);
-    google.options({ auth: oauth2Client });
-
-    const code = req.session.googleAuthCode || null;
-
-    if (!code) {
-      return callback(new Error('No code exists.'), null);
-    }
-
-    debug('Request googleToken by auth code', code);
-    oauth2Client.getToken(code, (err, tokens) => {
-      debug('Result of google.getToken()', err, tokens);
-      if (err) {
-        return callback(new Error('[googleAuth.handleCallback] Error to get token.'), null);
-      }
-
-      oauth2Client.credentials = tokens;
-
-      const oauth2 = google.oauth2('v2');
-      oauth2.userinfo.get({}, (err, response) => {
-        debug('Response of oauth2.userinfo.get', err, response);
-        if (err) {
-          return callback(new Error('[googleAuth.handleCallback] Error while proceccing userinfo.get.'), null);
-        }
-
-        const data = response.data;
-        data.user_id = data.id; // This is for B.C. (tokeninfo をつかっている前提のコードに対してのもの)
-        return callback(null, data);
-      });
-    });
-  };
-
-  return lib;
-};

+ 5 - 3
src/server/util/importer.js

@@ -7,10 +7,12 @@
 module.exports = (crowi) => {
   const logger = require('@alias/logger')('growi:util:importer');
   const esa = require('esa-nodejs');
-  const config = crowi.getConfig();
   const createGrowiPages = require('./createGrowiPagesFromImports')(crowi);
   const restQiitaAPIService = crowi.getRestQiitaAPIService();
 
+  const configManager = crowi.configManager;
+  const getConfig = configManager.getConfig;
+
   const importer = {};
   let esaClient = {};
 
@@ -19,8 +21,8 @@ module.exports = (crowi) => {
    */
   importer.initializeEsaClient = () => {
     esaClient = esa({
-      team:        config.crowi['importer:esa:team_name'],
-      accessToken: config.crowi['importer:esa:access_token'],
+      team:        getConfig('crowi', 'importer:esa:team_name'),
+      accessToken: getConfig('crowi', 'importer:esa:access_token'),
     });
     logger.debug('initialize esa importer');
   };

+ 15 - 15
src/server/util/mailer.js

@@ -6,24 +6,24 @@ module.exports = function(crowi) {
   const debug = require('debug')('growi:lib:mailer');
   const nodemailer = require('nodemailer');
   const swig = require('swig-templates');
-  const Config = crowi.model('Config');
-  const config = crowi.getConfig();
-  const mailConfig = {};
 
+  const { configManager, appService } = crowi;
+
+  const mailConfig = {};
   let mailer = {};
 
   function createSMTPClient(option) {
     debug('createSMTPClient option', option);
     if (!option) {
       option = { // eslint-disable-line no-param-reassign
-        host: config.crowi['mail:smtpHost'],
-        port: config.crowi['mail:smtpPort'],
+        host: configManager.getConfig('crowi', 'mail:smtpHost'),
+        port: configManager.getConfig('crowi', 'mail:smtpPort'),
       };
 
-      if (config.crowi['mail:smtpUser'] && config.crowi['mail:smtpPassword']) {
+      if (configManager.getConfig('crowi', 'mail:smtpUser') && configManager.getConfig('crowi', 'mail:smtpPassword')) {
         option.auth = {
-          user: config.crowi['mail:smtpUser'],
-          pass: config.crowi['mail:smtpPassword'],
+          user: configManager.getConfig('crowi', 'mail:smtpUser'),
+          pass: configManager.getConfig('crowi', 'mail:smtpPassword'),
         };
       }
       if (option.port === 465) {
@@ -41,8 +41,8 @@ module.exports = function(crowi) {
   function createSESClient(option) {
     if (!option) {
       option = { // eslint-disable-line no-param-reassign
-        accessKeyId: config.crowi['aws:accessKeyId'],
-        secretAccessKey: config.crowi['aws:secretAccessKey'],
+        accessKeyId: configManager.getConfig('crowi', 'aws:accessKeyId'),
+        secretAccessKey: configManager.getConfig('crowi', 'aws:secretAccessKey'),
       };
     }
 
@@ -54,17 +54,17 @@ module.exports = function(crowi) {
   }
 
   function initialize() {
-    if (!config.crowi['mail:from']) {
+    if (!configManager.getConfig('crowi', 'mail:from')) {
       mailer = undefined;
       return;
     }
 
-    if (config.crowi['mail:smtpHost'] && config.crowi['mail:smtpPort']
+    if (configManager.getConfig('crowi', 'mail:smtpHost') && configManager.getConfig('crowi', 'mail:smtpPort')
     ) {
       // SMTP 設定がある場合はそれを優先
       mailer = createSMTPClient();
     }
-    else if (config.crowi['aws:accessKeyId'] && config.crowi['aws:secretAccessKey']) {
+    else if (configManager.getConfig('crowi', 'aws:accessKeyId') && configManager.getConfig('crowi', 'aws:secretAccessKey')) {
       // AWS 設定がある場合はSESを設定
       mailer = createSESClient();
     }
@@ -72,8 +72,8 @@ module.exports = function(crowi) {
       mailer = undefined;
     }
 
-    mailConfig.from = config.crowi['mail:from'];
-    mailConfig.subject = `${Config.appTitle(config)}からのメール`;
+    mailConfig.from = configManager.getConfig('crowi', 'mail:from');
+    mailConfig.subject = `${appService.getAppTitle()}からのメール`;
 
     debug('mailer initialized');
   }

+ 171 - 204
src/server/util/middlewares.js

@@ -4,50 +4,29 @@ const pathUtils = require('growi-commons').pathUtils;
 const md5 = require('md5');
 const entities = require('entities');
 
+module.exports = (crowi, app) => {
+  const { configManager, appService } = crowi;
 
-exports.csrfKeyGenerator = function(crowi, app) {
-  return function(req, res, next) {
-    const csrfKey = (req.session && req.session.id) || 'anon';
-
-    if (req.csrfToken === null) {
-      req.csrfToken = crowi.getTokens().create(csrfKey);
-    }
-
-    next();
-  };
-};
+  const middlewares = {};
 
-exports.loginChecker = function(crowi, app) {
-  const User = crowi.model('User');
-  return async function(req, res, next) {
-    let user = null;
+  middlewares.csrfKeyGenerator = function() {
+    return function(req, res, next) {
+      const csrfKey = (req.session && req.session.id) || 'anon';
 
-    try {
-      // session に user object が入ってる
-      if (req.session.user && '_id' in req.session.user) {
-        user = await User.findById(req.session.user._id).populate(User.IMAGE_POPULATION);
+      if (req.csrfToken === null) {
+        req.csrfToken = crowi.getTokens().create(csrfKey);
       }
 
-      req.user = user;
-      req.session.user = user;
-      res.locals.user = req.user;
       next();
-    }
-    catch (err) {
-      next(err);
-    }
+    };
   };
-};
 
-exports.loginCheckerForPassport = function(crowi, app) {
-  return function(req, res, next) {
+  middlewares.loginCheckerForPassport = function(req, res, next) {
     res.locals.user = req.user;
     next();
   };
-};
 
-exports.csrfVerify = function(crowi, app) {
-  return function(req, res, next) {
+  middlewares.csrfVerify = function(req, res, next) {
     const token = req.body._csrf || req.query._csrf || null;
     const csrfKey = (req.session && req.session.id) || 'anon';
 
@@ -65,134 +44,132 @@ exports.csrfVerify = function(crowi, app) {
     logger.warn('csrf verification failed. return 403', csrfKey, token);
     return res.sendStatus(403);
   };
-};
 
-exports.swigFunctions = function(crowi, app) {
-  return function(req, res, next) {
-    require('../util/swigFunctions')(crowi, app, req, res.locals);
-    next();
+  middlewares.swigFunctions = function() {
+    return function(req, res, next) {
+      require('../util/swigFunctions')(crowi, app, req, res.locals);
+      next();
+    };
   };
-};
 
-exports.swigFilters = function(crowi, app, swig) {
-  // define a function for Gravatar
-  const generateGravatarSrc = function(user) {
-    const email = user.email || '';
-    const hash = md5(email.trim().toLowerCase());
-    return `https://gravatar.com/avatar/${hash}`;
-  };
+  middlewares.swigFilters = function(swig) {
+    // define a function for Gravatar
+    const generateGravatarSrc = function(user) {
+      const email = user.email || '';
+      const hash = md5(email.trim().toLowerCase());
+      return `https://gravatar.com/avatar/${hash}`;
+    };
+
+    // define a function for uploaded picture
+    const getUploadedPictureSrc = function(user) {
+      if (user.image) {
+        return user.image;
+      }
+      if (user.imageAttachment != null) {
+        return user.imageAttachment.filePathProxied;
+      }
 
-  // define a function for uploaded picture
-  const getUploadedPictureSrc = function(user) {
-    if (user.image) {
-      return user.image;
-    }
-    if (user.imageAttachment != null) {
-      return user.imageAttachment.filePathProxied;
-    }
+      return '/images/icons/user.svg';
+    };
 
-    return '/images/icons/user.svg';
-  };
 
+    return function(req, res, next) {
+      swig.setFilter('path2name', (string) => {
+        const name = string.replace(/(\/)$/, '');
 
-  return function(req, res, next) {
-    swig.setFilter('path2name', (string) => {
-      const name = string.replace(/(\/)$/, '');
+        if (name.match(/.+\/([^/]+\/\d{4}\/\d{2}\/\d{2})$/)) { // /.../hoge/YYYY/MM/DD 形式のページ
+          return name.replace(/.+\/([^/]+\/\d{4}\/\d{2}\/\d{2})$/, '$1');
+        }
+        if (name.match(/.+\/([^/]+\/\d{4}\/\d{2})$/)) { // /.../hoge/YYYY/MM 形式のページ
+          return name.replace(/.+\/([^/]+\/\d{4}\/\d{2})$/, '$1');
+        }
+        if (name.match(/.+\/([^/]+\/\d{4})$/)) { // /.../hoge/YYYY 形式のページ
+          return name.replace(/.+\/([^/]+\/\d{4})$/, '$1');
+        }
 
-      if (name.match(/.+\/([^/]+\/\d{4}\/\d{2}\/\d{2})$/)) { // /.../hoge/YYYY/MM/DD 形式のページ
-        return name.replace(/.+\/([^/]+\/\d{4}\/\d{2}\/\d{2})$/, '$1');
-      }
-      if (name.match(/.+\/([^/]+\/\d{4}\/\d{2})$/)) { // /.../hoge/YYYY/MM 形式のページ
-        return name.replace(/.+\/([^/]+\/\d{4}\/\d{2})$/, '$1');
-      }
-      if (name.match(/.+\/([^/]+\/\d{4})$/)) { // /.../hoge/YYYY 形式のページ
-        return name.replace(/.+\/([^/]+\/\d{4})$/, '$1');
-      }
+        return name.replace(/.+\/(.+)?$/, '$1'); // ページの末尾を拾う
+      });
 
-      return name.replace(/.+\/(.+)?$/, '$1'); // ページの末尾を拾う
-    });
-
-    swig.setFilter('normalizeDateInPath', (path) => {
-      const patterns = [
-        [/20(\d{2})(\d{2})(\d{2})(.+)/g, '20$1/$2/$3/$4'],
-        [/20(\d{2})(\d{2})(\d{2})/g, '20$1/$2/$3'],
-        [/20(\d{2})(\d{2})(.+)/g, '20$1/$2/$3'],
-        [/20(\d{2})(\d{2})/g, '20$1/$2'],
-        [/20(\d{2})_(\d{1,2})_(\d{1,2})_?(.+)/g, '20$1/$2/$3/$4'],
-        [/20(\d{2})_(\d{1,2})_(\d{1,2})/g, '20$1/$2/$3'],
-        [/20(\d{2})_(\d{1,2})_?(.+)/g, '20$1/$2/$3'],
-        [/20(\d{2})_(\d{1,2})/g, '20$1/$2'],
-      ];
-
-      for (let i = 0; i < patterns.length; i++) {
-        const mat = patterns[i][0];
-        const rep = patterns[i][1];
-        if (path.match(mat)) {
-          return path.replace(mat, rep);
+      swig.setFilter('normalizeDateInPath', (path) => {
+        const patterns = [
+          [/20(\d{2})(\d{2})(\d{2})(.+)/g, '20$1/$2/$3/$4'],
+          [/20(\d{2})(\d{2})(\d{2})/g, '20$1/$2/$3'],
+          [/20(\d{2})(\d{2})(.+)/g, '20$1/$2/$3'],
+          [/20(\d{2})(\d{2})/g, '20$1/$2'],
+          [/20(\d{2})_(\d{1,2})_(\d{1,2})_?(.+)/g, '20$1/$2/$3/$4'],
+          [/20(\d{2})_(\d{1,2})_(\d{1,2})/g, '20$1/$2/$3'],
+          [/20(\d{2})_(\d{1,2})_?(.+)/g, '20$1/$2/$3'],
+          [/20(\d{2})_(\d{1,2})/g, '20$1/$2'],
+        ];
+
+        for (let i = 0; i < patterns.length; i++) {
+          const mat = patterns[i][0];
+          const rep = patterns[i][1];
+          if (path.match(mat)) {
+            return path.replace(mat, rep);
+          }
         }
-      }
 
-      return path;
-    });
+        return path;
+      });
 
-    swig.setFilter('datetz', (input, format) => {
-      // timezone
-      const swigFilters = require('swig-templates/lib/filters');
-      return swigFilters.date(input, format, app.get('tzoffset'));
-    });
+      swig.setFilter('datetz', (input, format) => {
+        // timezone
+        const swigFilters = require('swig-templates/lib/filters');
+        return swigFilters.date(input, format, app.get('tzoffset'));
+      });
 
-    swig.setFilter('nl2br', (string) => {
-      return string
-        .replace(/\n/g, '<br>');
-    });
+      swig.setFilter('nl2br', (string) => {
+        return string
+          .replace(/\n/g, '<br>');
+      });
 
-    swig.setFilter('removeTrailingSlash', (string) => {
-      return pathUtils.removeTrailingSlash(string);
-    });
+      swig.setFilter('removeTrailingSlash', (string) => {
+        return pathUtils.removeTrailingSlash(string);
+      });
 
-    swig.setFilter('addTrailingSlash', (string) => {
-      return pathUtils.addTrailingSlash(string);
-    });
+      swig.setFilter('addTrailingSlash', (string) => {
+        return pathUtils.addTrailingSlash(string);
+      });
 
-    swig.setFilter('presentation', (string) => {
-      // 手抜き
-      return string
-        .replace(/\s(https?.+(jpe?g|png|gif))\s/, '\n\n\n![]($1)\n\n\n');
-    });
+      swig.setFilter('presentation', (string) => {
+        // 手抜き
+        return string
+          .replace(/\s(https?.+(jpe?g|png|gif))\s/, '\n\n\n![]($1)\n\n\n');
+      });
 
-    swig.setFilter('gravatar', generateGravatarSrc);
-    swig.setFilter('uploadedpicture', getUploadedPictureSrc);
+      swig.setFilter('gravatar', generateGravatarSrc);
+      swig.setFilter('uploadedpicture', getUploadedPictureSrc);
 
-    swig.setFilter('picture', (user) => {
-      if (!user) {
-        return '/images/icons/user.svg';
-      }
+      swig.setFilter('picture', (user) => {
+        if (!user) {
+          return '/images/icons/user.svg';
+        }
 
-      if (user.isGravatarEnabled === true) {
-        return generateGravatarSrc(user);
-      }
+        if (user.isGravatarEnabled === true) {
+          return generateGravatarSrc(user);
+        }
 
-      return getUploadedPictureSrc(user);
-    });
+        return getUploadedPictureSrc(user);
+      });
 
-    swig.setFilter('encodeHTML', (string) => {
-      return entities.encodeHTML(string);
-    });
+      swig.setFilter('encodeHTML', (string) => {
+        return entities.encodeHTML(string);
+      });
 
-    swig.setFilter('preventXss', (string) => {
-      return crowi.xss.process(string);
-    });
+      swig.setFilter('preventXss', (string) => {
+        return crowi.xss.process(string);
+      });
 
-    swig.setFilter('slice', (list, start, end) => {
-      return list.slice(start, end);
-    });
+      swig.setFilter('slice', (list, start, end) => {
+        return list.slice(start, end);
+      });
 
-    next();
+      next();
+    };
   };
-};
 
-exports.adminRequired = function() {
-  return function(req, res, next) {
+  middlewares.adminRequired = function(req, res, next) {
     // check the user logged in
     //  make sure that req.user isn't username/email string to login which is set by basic-auth-connect
     if (req.user != null && (req.user instanceof Object) && '_id' in req.user) {
@@ -204,61 +181,54 @@ exports.adminRequired = function() {
     }
     return res.redirect('/login');
   };
-};
 
-/**
- * require login handler
- *
- * @param {any} crowi
- * @param {any} app
- * @param {boolean} isStrictly whethere strictly restricted (default true)
- */
-exports.loginRequired = function(crowi, app, isStrictly = true) {
-  return function(req, res, next) {
-    const User = crowi.model('User');
-
-    // when the route is not strictly restricted
-    if (!isStrictly) {
-      const config = req.config;
-      const Config = crowi.model('Config');
-
-      // when allowed to read
-      if (Config.isGuestAllowedToRead(config)) {
-        return next();
+  /**
+   * require login handler
+   *
+   * @param {boolean} isStrictly whethere strictly restricted (default true)
+   */
+  middlewares.loginRequired = function(isStrictly = true) {
+    return function(req, res, next) {
+      const User = crowi.model('User');
+
+      // when the route is not strictly restricted
+      if (!isStrictly) {
+        // when allowed to read
+        if (crowi.aclService.getIsGuestAllowedToRead()) {
+          return next();
+        }
       }
-    }
 
-    // check the user logged in
-    //  make sure that req.user isn't username/email string to login which is set by basic-auth-connect
-    if (req.user != null && (req.user instanceof Object) && '_id' in req.user) {
-      if (req.user.status === User.STATUS_ACTIVE) {
-        // Active の人だけ先に進める
-        return next();
-      }
-      if (req.user.status === User.STATUS_REGISTERED) {
-        return res.redirect('/login/error/registered');
-      }
-      if (req.user.status === User.STATUS_SUSPENDED) {
-        return res.redirect('/login/error/suspended');
-      }
-      if (req.user.status === User.STATUS_INVITED) {
-        return res.redirect('/login/invited');
+      // check the user logged in
+      //  make sure that req.user isn't username/email string to login which is set by basic-auth-connect
+      if (req.user != null && (req.user instanceof Object) && '_id' in req.user) {
+        if (req.user.status === User.STATUS_ACTIVE) {
+          // Active の人だけ先に進める
+          return next();
+        }
+        if (req.user.status === User.STATUS_REGISTERED) {
+          return res.redirect('/login/error/registered');
+        }
+        if (req.user.status === User.STATUS_SUSPENDED) {
+          return res.redirect('/login/error/suspended');
+        }
+        if (req.user.status === User.STATUS_INVITED) {
+          return res.redirect('/login/invited');
+        }
       }
-    }
 
-    // is api path
-    const path = req.path || '';
-    if (path.match(/^\/_api\/.+$/)) {
-      return res.sendStatus(403);
-    }
+      // is api path
+      const path = req.path || '';
+      if (path.match(/^\/_api\/.+$/)) {
+        return res.sendStatus(403);
+      }
 
-    req.session.jumpTo = req.originalUrl;
-    return res.redirect('/login');
+      req.session.jumpTo = req.originalUrl;
+      return res.redirect('/login');
+    };
   };
-};
 
-exports.accessTokenParser = function(crowi, app) {
-  return function(req, res, next) {
+  middlewares.accessTokenParser = function(req, res, next) {
     // TODO: comply HTTP header of RFC6750 / Authorization: Bearer
     const accessToken = req.query.access_token || req.body.access_token || null;
     if (!accessToken) {
@@ -280,45 +250,42 @@ exports.accessTokenParser = function(crowi, app) {
         next();
       });
   };
-};
 
-// this is for Installer
-exports.applicationNotInstalled = function() {
-  return function(req, res, next) {
-    const config = req.config;
+  // this is for Installer
+  middlewares.applicationNotInstalled = async function(req, res, next) {
+    const isInstalled = await appService.isDBInitialized();
 
-    if (Object.keys(config.crowi).length !== 0) {
+    if (isInstalled) {
       req.flash('errorMessage', 'Application already installed.');
       return res.redirect('admin'); // admin以外はadminRequiredで'/'にリダイレクトされる
     }
 
     return next();
   };
-};
 
-exports.applicationInstalled = function() {
-  return function(req, res, next) {
-    const config = req.config;
+  middlewares.applicationInstalled = async function(req, res, next) {
+    const isInstalled = await appService.isDBInitialized();
 
-    if (Object.keys(config.crowi).length === 0) {
+    if (!isInstalled) {
       return res.redirect('/installer');
     }
 
     return next();
   };
-};
 
-exports.awsEnabled = function() {
-  return function(req, res, next) {
-    const config = req.config;
-    if (config.crowi['aws:region'] !== ''
-        && config.crowi['aws:bucket'] !== ''
-        && config.crowi['aws:accessKeyId'] !== ''
-        && config.crowi['aws:secretAccessKey'] !== '') {
-      req.flash('globalError', 'AWS settings required to use this function. Please ask the administrator.');
-      return res.redirect('/');
-    }
+  middlewares.awsEnabled = function() {
+    return function(req, res, next) {
+      if (configManager.getConfig('crowi', 'aws:region') !== ''
+          && configManager.getConfig('crowi', 'aws:bucket') !== ''
+          && configManager.getConfig('crowi', 'aws:accessKeyId') !== ''
+          && configManager.getConfig('crowi', 'aws:secretAccessKey') !== '') {
+        req.flash('globalError', 'AWS settings required to use this function. Please ask the administrator.');
+        return res.redirect('/');
+      }
 
-    return next();
+      return next();
+    };
   };
+
+  return middlewares;
 };

+ 3 - 5
src/server/util/search.js

@@ -19,6 +19,7 @@ function SearchClient(crowi, esUri) {
   this.esUri = esUri;
   this.crowi = crowi;
   this.searchEvent = crowi.event('search');
+  this.configManager = this.crowi.configManager;
 
   // In Elasticsearch RegExp, we don't need to used ^ and $.
   // Ref: https://www.elastic.co/guide/en/elasticsearch/reference/5.6/query-dsl-regexp-query.html#_standard_operators
@@ -553,11 +554,8 @@ SearchClient.prototype.appendCriteriaForQueryString = function(query, queryStrin
 };
 
 SearchClient.prototype.filterPagesByViewer = async function(query, user, userGroups) {
-  const Config = this.crowi.model('Config');
-  const config = this.crowi.getConfig();
-
-  const showPagesRestrictedByOwner = !Config.hidePagesRestrictedByOwnerInList(config);
-  const showPagesRestrictedByGroup = !Config.hidePagesRestrictedByGroupInList(config);
+  const showPagesRestrictedByOwner = !this.configManager.getConfig('crowi', 'security:list-policy:hideRestrictedByOwner');
+  const showPagesRestrictedByGroup = !this.configManager.getConfig('crowi', 'security:list-policy:hidePagesRestrictedByGroupInList');
 
   query = this.initializeBoolQuery(query); // eslint-disable-line no-param-reassign
 

+ 17 - 16
src/server/util/slack.js

@@ -8,16 +8,15 @@ const urljoin = require('url-join');
 /* eslint-disable no-use-before-define */
 
 module.exports = function(crowi) {
-  const config = crowi.getConfig();
-  const Config = crowi.model('Config');
   const Slack = require('slack-node');
+  const { configManager } = crowi;
 
   const slack = {};
 
   const postWithIwh = function(messageObj) {
     return new Promise((resolve, reject) => {
       const client = new Slack();
-      client.setWebhook(config.notification['slack:incomingWebhookUrl']);
+      client.setWebhook(configManager.getConfig('notification', 'slack:incomingWebhookUrl'));
       client.webhook(messageObj, (err, res) => {
         if (err) {
           debug('Post error', err, res);
@@ -31,7 +30,7 @@ module.exports = function(crowi) {
 
   const postWithWebApi = function(messageObj) {
     return new Promise((resolve, reject) => {
-      const client = new Slack(config.notification['slack:token']);
+      const client = new Slack(configManager.getConfig('notification', 'slack:token'));
       // stringify attachments
       if (messageObj.attachments != null) {
         messageObj.attachments = JSON.stringify(messageObj.attachments);
@@ -48,7 +47,7 @@ module.exports = function(crowi) {
   };
 
   const convertMarkdownToMarkdown = function(body) {
-    const url = crowi.configManager.getSiteUrl();
+    const url = crowi.appService.getSiteUrl();
 
     return body
       .replace(/\n\*\s(.+)/g, '\n• $1')
@@ -107,7 +106,8 @@ module.exports = function(crowi) {
   };
 
   const prepareSlackMessageForPage = function(page, user, channel, updateType, previousRevision) {
-    const url = crowi.configManager.getSiteUrl();
+    const appTitle = crowi.appService.getAppTitle();
+    const url = crowi.appService.getSiteUrl();
     let body = page.revision.body;
 
     if (updateType === 'create') {
@@ -133,7 +133,7 @@ module.exports = function(crowi) {
 
     const message = {
       channel: `#${channel}`,
-      username: Config.appTitle(config),
+      username: appTitle,
       text: getSlackMessageTextForPage(page.path, page.id, user, updateType),
       attachments: [attachment],
     };
@@ -142,7 +142,8 @@ module.exports = function(crowi) {
   };
 
   const prepareSlackMessageForComment = function(comment, user, channel, path) {
-    const url = crowi.configManager.getSiteUrl();
+    const appTitle = crowi.appService.getAppTitle();
+    const url = crowi.appService.getSiteUrl();
     const body = prepareAttachmentTextForComment(comment);
 
     const attachment = {
@@ -159,7 +160,7 @@ module.exports = function(crowi) {
 
     const message = {
       channel: `#${channel}`,
-      username: Config.appTitle(config),
+      username: appTitle,
       text: getSlackMessageTextForComment(path, String(comment.page), user),
       attachments: [attachment],
     };
@@ -169,7 +170,7 @@ module.exports = function(crowi) {
 
   const getSlackMessageTextForPage = function(path, pageId, user, updateType) {
     let text;
-    const url = crowi.configManager.getSiteUrl();
+    const url = crowi.appService.getSiteUrl();
 
     const pageUrl = `<${urljoin(url, pageId)}|${path}>`;
     if (updateType === 'create') {
@@ -183,7 +184,7 @@ module.exports = function(crowi) {
   };
 
   const getSlackMessageTextForComment = function(path, pageId, user) {
-    const url = crowi.configManager.getSiteUrl();
+    const url = crowi.appService.getSiteUrl();
     const pageUrl = `<${urljoin(url, pageId)}|${path}>`;
     const text = `:speech_balloon: ${user.username} commented on ${pageUrl}`;
 
@@ -205,23 +206,23 @@ module.exports = function(crowi) {
 
   const slackPost = (messageObj) => {
     // when incoming Webhooks is prioritized
-    if (Config.isIncomingWebhookPrioritized(config)) {
-      if (Config.hasSlackIwhUrl(config)) {
+    if (configManager.getConfig('notification', 'slack:isIncomingWebhookPrioritized')) {
+      if (configManager.getConfig('notification', 'slack:incomingWebhookUrl')) {
         debug('posting message with IncomingWebhook');
         return postWithIwh(messageObj);
       }
-      if (Config.hasSlackToken(config)) {
+      if (configManager.getConfig('notification', 'slack:token')) {
         debug('posting message with Web API');
         return postWithWebApi(messageObj);
       }
     }
     // else
     else {
-      if (Config.hasSlackToken(config)) {
+      if (configManager.getConfig('notification', 'slack:token')) {
         debug('posting message with Web API');
         return postWithWebApi(messageObj);
       }
-      if (Config.hasSlackIwhUrl(config)) {
+      if (configManager.getConfig('notification', 'slack:incomingWebhookUrl')) {
         debug('posting message with IncomingWebhook');
         return postWithIwh(messageObj);
       }

+ 26 - 174
src/server/util/swigFunctions.js

@@ -2,10 +2,16 @@ module.exports = function(crowi, app, req, locals) {
   const debug = require('debug')('growi:lib:swigFunctions');
   const stringWidth = require('string-width');
   const Page = crowi.model('Page');
-  const Config = crowi.model('Config');
   const User = crowi.model('User');
-  const passportService = crowi.passportService;
-  const cdnResourcesService = crowi.cdnResourcesService;
+  const {
+    configManager,
+    cdnResourcesService,
+    passportService,
+    appService,
+    aclService,
+    fileUploadService,
+    customizeService,
+  } = crowi;
   debug('initializing swigFunctions');
 
   locals.nodeVersion = function() {
@@ -43,38 +49,25 @@ module.exports = function(crowi, app, req, locals) {
   /**
    * @see ConfigManager#getConfig
    */
-  locals.getConfig = function(namespace, key) {
-    return crowi.configManager.getConfig(namespace, key);
-  };
+  locals.getConfig = configManager.getConfig.bind(configManager);
 
   /**
    * **Do not use this unless absolutely necessary. Use getConfig instead.**
    */
-  locals.getConfigFromDB = function(namespace, key) {
-    return crowi.configManager.getConfigFromDB(namespace, key);
-  };
-  /**
-   * **Do not use this unless absolutely necessary. Use getConfig instead.**
-   */
-  locals.getConfigFromEnvVars = function(namespace, key) {
-    return crowi.configManager.getConfigFromEnvVars(namespace, key);
-  };
+  locals.getConfigFromDB = configManager.getConfigFromDB.bind(configManager);
 
   /**
-   * return app title
+   * **Do not use this unless absolutely necessary. Use getConfig instead.**
    */
-  locals.appTitle = function() {
-    const config = crowi.getConfig();
-    return crowi.xss.process(Config.appTitle(config));
-  };
+  locals.getConfigFromEnvVars = configManager.getConfigFromEnvVars.bind(configManager);
 
   /**
-   * return app-global language
+   * pass service class to swig
    */
-  locals.appGlobalLang = function() {
-    const config = crowi.getConfig();
-    return Config.globalLang(config);
-  };
+  locals.appService = appService;
+  locals.aclService = aclService;
+  locals.fileUploadService = fileUploadService;
+  locals.customizeService = customizeService;
 
   locals.noCdn = function() {
     return !!process.env.NO_CDN;
@@ -101,88 +94,30 @@ module.exports = function(crowi, app, req, locals) {
     return cdnResourcesService.getHighlightJsStyleTag(styleName);
   };
 
-  /**
-   * return true if enabled
-   */
-  locals.isEnabledPassport = function() {
-    const config = crowi.getConfig();
-    return Config.isEnabledPassport(config);
-  };
-
-  /**
-   * return true if local strategy has been setup successfully
-   *  used whether restarting the server needed
-   */
-  locals.isPassportLocalStrategySetup = function() {
-    return passportService != null && passportService.isLocalStrategySetup;
-  };
-
   /**
    * return true if enabled and strategy has been setup successfully
    */
   locals.isLdapSetup = function() {
-    const config = crowi.getConfig();
-    return Config.isEnabledPassport(config) && Config.isEnabledPassportLdap(config) && passportService.isLdapStrategySetup;
+    return (
+      configManager.getConfig('crowi', 'security:passport-ldap:isEnabled')
+      && passportService.isLdapStrategySetup
+    );
   };
 
   /**
    * return true if enabled but strategy has some problem
    */
   locals.isLdapSetupFailed = function() {
-    const config = crowi.getConfig();
-    return Config.isEnabledPassport(config) && Config.isEnabledPassportLdap(config) && !passportService.isLdapStrategySetup;
-  };
-
-  locals.passportSamlLoginEnabled = function() {
-    return locals.isEnabledPassport() && locals.getConfig('crowi', 'security:passport-saml:isEnabled');
+    return (
+      configManager.getConfig('crowi', 'security:passport-ldap:isEnabled')
+      && !passportService.isLdapStrategySetup
+    );
   };
 
   locals.getSamlMissingMandatoryConfigKeys = function() {
-    // return an empty array if Passport is not enabled
-    // because crowi.passportService is null.
-    if (!locals.isEnabledPassport()) {
-      return [];
-    }
-
     return crowi.passportService.getSamlMissingMandatoryConfigKeys();
   };
 
-  locals.googleLoginEnabled = function() {
-    // return false if Passport is enabled
-    // because official crowi mechanism is not used.
-    if (locals.isEnabledPassport()) {
-      return false;
-    }
-
-    const config = crowi.getConfig();
-    return config.crowi['google:clientId'] && config.crowi['google:clientSecret'];
-  };
-
-  locals.passportGoogleLoginEnabled = function() {
-    const config = crowi.getConfig();
-    return locals.isEnabledPassport() && config.crowi['security:passport-google:isEnabled'];
-  };
-
-  locals.passportGitHubLoginEnabled = function() {
-    const config = crowi.getConfig();
-    return locals.isEnabledPassport() && config.crowi['security:passport-github:isEnabled'];
-  };
-
-  locals.passportTwitterLoginEnabled = function() {
-    const config = crowi.getConfig();
-    return locals.isEnabledPassport() && config.crowi['security:passport-twitter:isEnabled'];
-  };
-
-  locals.passportOidcLoginEnabled = function() {
-    const config = crowi.getConfig();
-    return locals.isEnabledPassport() && config.crowi['security:passport-oidc:isEnabled'];
-  };
-
-  locals.passportBasicLoginEnabled = function() {
-    const config = crowi.getConfig();
-    return locals.isEnabledPassport() && config.crowi['security:passport-basic:isEnabled'];
-  };
-
   locals.searchConfigured = function() {
     if (crowi.getSearcher()) {
       return true;
@@ -194,89 +129,6 @@ module.exports = function(crowi, app, req, locals) {
     return process.env.HACKMD_URI != null;
   };
 
-  locals.isEnabledPlugins = function() {
-    const config = crowi.getConfig();
-    return Config.isEnabledPlugins(config);
-  };
-
-  locals.isEnabledLinebreaks = function() {
-    const config = crowi.getConfig();
-    return Config.isEnabledLinebreaks(config);
-  };
-
-  locals.isEnabledLinebreaksInComments = function() {
-    const config = crowi.getConfig();
-    return Config.isEnabledLinebreaksInComments(config);
-  };
-
-  locals.customCss = function() {
-    return Config.customCss();
-  };
-
-  locals.pageBreakSeparator = function() {
-    const config = crowi.getConfig();
-    return Config.pageBreakSeparator(config);
-  };
-
-  locals.pageBreakCustomSeparator = function() {
-    const config = crowi.getConfig();
-    return Config.pageBreakCustomSeparator(config);
-  };
-
-  locals.customScript = function() {
-    return Config.customScript();
-  };
-
-  locals.customHeader = function() {
-    const config = crowi.getConfig();
-    return Config.customHeader(config);
-  };
-
-  locals.theme = function() {
-    const config = crowi.getConfig();
-    return Config.theme(config);
-  };
-
-  locals.customTitle = function(page) {
-    const config = crowi.getConfig();
-    return Config.customTitle(config, page);
-  };
-
-  locals.behaviorType = function() {
-    const config = crowi.getConfig();
-    return Config.behaviorType(config);
-  };
-
-  locals.layoutType = function() {
-    const config = crowi.getConfig();
-    return Config.layoutType(config);
-  };
-
-  locals.highlightJsStyle = function() {
-    const config = crowi.getConfig();
-    return Config.highlightJsStyle(config);
-  };
-
-  locals.highlightJsStyleBorder = function() {
-    const config = crowi.getConfig();
-    return Config.highlightJsStyleBorder(config);
-  };
-
-  locals.isEnabledTimeline = function() {
-    const config = crowi.getConfig();
-    return Config.isEnabledTimeline(config);
-  };
-
-  locals.isUploadable = function() {
-    const config = crowi.getConfig();
-    return Config.isUploadable(config);
-  };
-
-  locals.isEnabledAttachTitleHeader = function() {
-    const config = crowi.getConfig();
-    return Config.isEnabledAttachTitleHeader(config);
-  };
-
   locals.parentPath = function(path) {
     if (path === '/') {
       return path;

+ 21 - 21
src/server/views/admin/app.html

@@ -1,6 +1,6 @@
 {% extends '../layout/admin.html' %}
 
-{% block html_title %}{{ customTitle(t('App settings')) }}{% endblock %}
+{% block html_title %}{{ customizeService.generateCustomTitle(t('App settings')) }}{% endblock %}
 
 {% block head_warn_alert_siteurl_undefined %} {# remove including block for './widget/alert_siteurl_undefined.html' #}
 {% endblock %}
@@ -45,7 +45,7 @@
                    id="settingForm[app:title]"
                    type="text"
                    name="settingForm[app:title]"
-                   value="{{ settingForm['app:title'] | default('') }}"
+                   value="{{ getConfig('crowi', 'app:title') | default('') }}"
                    placeholder="GROWI">
             <p class="help-block">{{ t("app_setting.sitename_change") }}</p>
           </div>
@@ -58,8 +58,8 @@
                    id="settingForm[app:confidential]"
                    type="text"
                    name="settingForm[app:confidential]"
-                   value="{{ settingForm['app:confidential'] }}"
-                   placeholder="{{ t('app_setting. ex): internal use only') }}">
+                   value="{{ getConfig('crowi', 'app:confidential') | default('') }}"
+                   placeholder="{{ t('app_setting. ex&rpar;: internal use only') }}">
             <p class="help-block">{{ t("app_setting.header_content") }}</p>
           </div>
         </div>
@@ -72,7 +72,7 @@
                        id="radioLangEn"
                        name="settingForm[app:globalLang]"
                        value="{{ consts.language.LANG_EN_US }}"
-                       {% if appGlobalLang() == consts.language.LANG_EN_US %}checked="checked"{% endif %}>
+                       {% if getConfig('crowi', 'app:globalLang') == consts.language.LANG_EN_US %}checked="checked"{% endif %}>
                 <label for="radioLangEn">{{ t('English') }}</label>
             </div>
             <div class="radio radio-primary radio-inline">
@@ -80,7 +80,7 @@
                        id="radioLangJa"
                        name="settingForm[app:globalLang]"
                        value="{{ consts.language.LANG_JA }}"
-                       {% if appGlobalLang() == consts.language.LANG_JA %}checked="checked"{% endif %}>
+                       {% if getConfig('crowi', 'app:globalLang') == consts.language.LANG_JA %}checked="checked"{% endif %}>
                 <label for="radioLangJa">{{ t('Japanese') }}</label>
             </div>
           </div>
@@ -94,8 +94,8 @@
                      id="cbFileUpload"
                      name="settingForm[app:fileUpload]"
                      value="1"
-                     {% if settingForm['app:fileUpload'] %}checked{% endif %}
-                     {% if not isUploadable() %}disabled="disabled"{% endif %}>
+                     {% if getConfig('crowi', 'app:fileUpload') %}checked{% endif %}
+                     {% if not fileUploadService.getIsUploadable() %}disabled="disabled"{% endif %}>
               <label for="cbFileUpload">
                 {{ t("app_setting.enable_files_except_image") }}
               </label>
@@ -181,7 +181,7 @@
                    type="text"
                    name="settingForm[mail:from]"
                    placeholder="{{ t('eg') }} mail@growi.org"
-                   value="{{ settingForm['mail:from'] }}">
+                   value="{{ getConfig('crowi', 'mail:from') | default('') }}">
           </div>
         </div>
 
@@ -192,14 +192,14 @@
             <input class="form-control"
                    type="text"
                    name="settingForm[mail:smtpHost]"
-                   value="{{ settingForm['mail:smtpHost']|default('') }}">
+                   value="{{ getConfig('crowi', 'mail:smtpHost') | default('') }}">
           </div>
           <div class="col-xs-2">
             <label>{{ t('app_setting.Port') }}</label>
             <input class="form-control"
                    type="text"
                    name="settingForm[mail:smtpPort]"
-                   value="{{ settingForm['mail:smtpPort']|default('') }}">
+                   value="{{ getConfig('crowi', 'mail:smtpPort') | default('') }}">
           </div>
         </div>
 
@@ -209,14 +209,14 @@
             <input class="form-control"
                    type="text"
                    name="settingForm[mail:smtpUser]"
-                   value="{{ settingForm['mail:smtpUser']|default('') }}">
+                   value="{{ getConfig('crowi', 'mail:smtpUser') | default('') }}">
           </div>
           <div class="col-xs-3">
             <label>{{ t('Password') }}</label>
             <input class="form-control"
                    type="password"
                    name="settingForm[mail:smtpPassword]"
-                   value="{{ settingForm['mail:smtpPassword']|default('') }}">
+                   value="{{ getConfig('crowi', 'mail:smtpPassword') | default('') }}">
           </div>
         </div>
 
@@ -248,7 +248,7 @@
                    type="text"
                    name="settingForm[aws:region]"
                    placeholder="例: ap-northeast-1"
-                   value="{{ settingForm['aws:region'] }}">
+                   value="{{ getConfig('crowi', 'aws:region') | default('') }}">
           </div>
         </div>
 
@@ -260,7 +260,7 @@
                    type="text"
                    name="settingForm[aws:bucket]"
                    placeholder="例: crowi"
-                   value="{{ settingForm['aws:bucket'] }}">
+                   value="{{ getConfig('crowi', 'aws:bucket') | default('') }}">
           </div>
         </div>
 
@@ -271,7 +271,7 @@
                    id="settingForm[aws:accessKeyId]"
                    type="text"
                    name="settingForm[aws:accessKeyId]"
-                   value="{{ settingForm['aws:accessKeyId'] }}">
+                   value="{{ getConfig('crowi', 'aws:accessKeyId') | default('') }}">
           </div>
 
         </div>
@@ -283,7 +283,7 @@
                    id="settingForm[aws:secretAccessKey]"
                    type="text"
                    name="settingForm[aws:secretAccessKey]"
-                   value="{{ settingForm['aws:secretAccessKey'] }}">
+                   value="{{ getConfig('crowi', 'aws:secretAccessKey') | default('') }}">
           </div>
         </div>
 
@@ -307,18 +307,18 @@
           <div class="col-xs-6">
 
             <div class="btn-group btn-toggle" data-toggle="buttons">
-              <label class="btn btn-default btn-rounded btn-outline {% if settingForm['plugin:isEnabledPlugins'] %}active{% endif %}" data-active-class="primary">
+              <label class="btn btn-default btn-rounded btn-outline {% if getConfig('crowi', 'plugin:isEnabledPlugins') %}active{% endif %}" data-active-class="primary">
                 <input name="settingForm[plugin:isEnabledPlugins]"
                        value="true"
                        type="radio"
-                       {% if true === settingForm['plugin:isEnabledPlugins'] %}checked{% endif %}>
+                       {% if true === getConfig('crowi', 'plugin:isEnabledPlugins') %}checked{% endif %}>
                 ON
               </label>
-              <label class="btn btn-default btn-rounded btn-outline {% if !settingForm['plugin:isEnabledPlugins'] %}active{% endif %}" data-active-class="default">
+              <label class="btn btn-default btn-rounded btn-outline {% if !getConfig('crowi', 'plugin:isEnabledPlugins') %}active{% endif %}" data-active-class="default">
                 <input name="settingForm[plugin:isEnabledPlugins]"
                        value="false"
                        type="radio"
-                       {% if !settingForm['plugin:isEnabledPlugins'] %}checked{% endif %}>
+                       {% if !getConfig('crowi', 'plugin:isEnabledPlugins') %}checked{% endif %}>
                 OFF
               </label>
             </div>

+ 11 - 18
src/server/views/admin/customize.html

@@ -1,20 +1,13 @@
 {% extends '../layout/admin.html' %}
 
-{% block html_title %}{{ customTitle(t('Customize')) }} {% endblock %}
+{% block html_title %}{{ customizeService.generateCustomTitle(t('Customize')) }} {% endblock %}
 
 {% block theme_css_block %}
-  {% if 'kibela' === layoutType() %}
-    {% if env === 'development' %}
-      <script src="{{ webpack_asset('styles/theme-kibela.js') }}"></script>
-    {% else %}
-      <link rel="stylesheet" href="{{ webpack_asset('styles/theme-kibela.css') }}">
-    {% endif %}
+  {% set themeName = getConfig('crowi', 'customize:theme') %}
+  {% if env === 'development' %}
+    <script src="{{ webpack_asset('styles/theme-' + themeName + '.js') }}"></script>
   {% else %}
-    {% if env === 'development' %}
-      <script src="{{ webpack_asset('styles/theme-' + theme() + '.js') }}"></script>
-    {% else %}
-    <link rel="stylesheet" id="jssDefault" {# append id for theme selector #} href="{{ webpack_asset('styles/theme-' + theme() + '.css') }}">
-    {% endif %}
+  <link rel="stylesheet" id="jssDefault" {# append id for theme selector #} href="{{ webpack_asset('styles/theme-' + themeName + '.css') }}">
   {% endif %}
 {% endblock %}
 
@@ -314,7 +307,7 @@
             <div class="col-xs-9">
               <select class="form-control selectpicker" name="settingForm[customize:highlightJsStyle]" onChange="selectHighlightJsStyle(event)" {% if noCdn() %}disabled{% endif %}>
                 {% for key in Object.keys(highlightJsCssSelectorOptions) %}
-                  <option value={{key}} {% if key == highlightJsStyle() %} selected {% endif %}>{{highlightJsCssSelectorOptions[key].name}}</option>
+                  <option value={{key}} {% if key == getConfig('crowi', 'customize:highlightJsStyle') %} selected {% endif %}>{{highlightJsCssSelectorOptions[key].name}}</option>
                 {% endfor %}
               </select>
               <p class="help-block text-warning">{{ t('customize_page.nocdn_desc') }}</p>
@@ -338,7 +331,7 @@
           </div>
 
           <div id="highlightJsCssContainer">
-            {{ cdnHighlightJsStyleTag(highlightJsStyle()) }}
+            {{ cdnHighlightJsStyleTag(getConfig('crowi', 'customize:highlightJsStyle')) }}
           </div>
 
           <p class="help-block">
@@ -388,7 +381,7 @@ export  $initHighlight;</code></pre>
 
           <div class="form-group">
             <div class="col-xs-12">
-              <input class="form-control" name="settingForm[customize:title]" value="{{ settingForm['customize:title'] }}"></input>
+              <input class="form-control" name="settingForm[customize:title]" value="{{ settingForm['customize:title'] | default('') }}"></input>
             </div>
           </div>
 
@@ -418,7 +411,7 @@ export  $initHighlight;</code></pre>
         <div class="form-group">
           <div class="col-xs-12">
             <div id="custom-header-editor"></div>
-            <input type="hidden" id="inputCustomHeader" name="settingForm[customize:header]" value="{{ settingForm['customize:header'] }}">
+            <input type="hidden" id="inputCustomHeader" name="settingForm[customize:header]" value="{{ settingForm['customize:header'] | default('') }}">
           </div>
           <div class="col-xs-12">
             <p class="help-block text-right">
@@ -450,7 +443,7 @@ export  $initHighlight;</code></pre>
         <div class="form-group">
           <div class="col-xs-12">
             <div id="custom-css-editor"></div>
-            <input type="hidden" id="inputCustomCss" name="settingForm[customize:css]" value="{{ settingForm['customize:css'] }}">
+            <input type="hidden" id="inputCustomCss" name="settingForm[customize:css]" value="{{ settingForm['customize:css'] | default('') }}">
           </div>
           <div class="col-xs-12">
             <p class="help-block text-right">
@@ -508,7 +501,7 @@ window.addEventListener('load', (event) => {
         <div class="form-group">
           <div class="col-xs-12">
             <div id="custom-script-editor"></div>
-            <input type="hidden" id="inputCustomScript" name="settingForm[customize:script]" value="{{ settingForm['customize:script'] }}">
+            <input type="hidden" id="inputCustomScript" name="settingForm[customize:script]" value="{{ settingForm['customize:script'] | default('') }}">
           </div>
           <div class="col-xs-12">
             <p class="help-block text-right">

+ 1 - 1
src/server/views/admin/external-accounts.html

@@ -1,6 +1,6 @@
 {% extends '../layout/admin.html' %}
 
-{% block html_title %}{{ customTitle(t('External Account management')) }}{% endblock %}
+{% block html_title %}{{ customizeService.generateCustomTitle(t('External Account management')) }}{% endblock %}
 
 {% block content_header %}
 <div class="header-wrap">

+ 1 - 1
src/server/views/admin/global-notification-detail.html

@@ -1,6 +1,6 @@
 {% extends '../layout/admin.html' %}
 
-{% block html_title %}{{ customTitle(t('Notification settings')) }}{% endblock %}
+{% block html_title %}{{ customizeService.generateCustomTitle(t('Notification settings')) }}{% endblock %}
 
 {% block content_header %}
 <div class="header-wrap">

+ 1 - 1
src/server/views/admin/importer.html

@@ -1,6 +1,6 @@
 {% extends '../layout/admin.html' %}
 
-{% block html_title %}{{ customTitle(t('Import Data')) }}{% endblock %}
+{% block html_title %}{{ customizeService.generateCustomTitle(t('Import Data')) }}{% endblock %}
 
 {% block content_header %}
 <div class="header-wrap">

+ 1 - 1
src/server/views/admin/index.html

@@ -1,6 +1,6 @@
 {% extends '../layout/admin.html' %}
 
-{% block html_title %}{{ customTitle(t('admin_top.Management Wiki')) }}{% endblock %}
+{% block html_title %}{{ customizeService.generateCustomTitle(t('admin_top.Management Wiki')) }}{% endblock %}
 
 {% block content_header %}
 <div class="header-wrap">

+ 1 - 1
src/server/views/admin/markdown.html

@@ -1,6 +1,6 @@
 {% extends '../layout/admin.html' %}
 
-{% block html_title %}{{ customTitle(t('Markdown settings')) }}
+{% block html_title %}{{ customizeService.generateCustomTitle(t('Markdown settings')) }}
  · {{ path }}{% endblock %}
 
 {% block content_header %}

+ 1 - 1
src/server/views/admin/notification.html

@@ -1,6 +1,6 @@
 {% extends '../layout/admin.html' %}
 
-{% block html_title %}{{ customTitle(t('Notification settings')) }}{% endblock %}
+{% block html_title %}{{ customizeService.generateCustomTitle(t('Notification settings')) }}{% endblock %}
 
 {% block content_header %}
 <div class="header-wrap">

+ 1 - 1
src/server/views/admin/search.html

@@ -1,6 +1,6 @@
 {% extends '../layout/admin.html' %}
 
-{% block html_title %}{{ customTitle(t('Full Text Search management')) }}{% endblock %}
+{% block html_title %}{{ customizeService.generateCustomTitle(t('Full Text Search management')) }}{% endblock %}
 
 {% block content_header %}
 <div class="header-wrap">

+ 11 - 170
src/server/views/admin/security.html

@@ -1,6 +1,6 @@
 {% extends '../layout/admin.html' %}
 
-{% block html_title %}{{ customTitle(t('security_settings')) }} · {% endblock %}
+{% block html_title %}{{ customizeService.generateCustomTitle(t('security_settings')) }} · {% endblock %}
 
 {% block content_header %}
 <div class="header-wrap">
@@ -40,34 +40,12 @@
         <fieldset>
         <legend class="alert-anchor">{{ t('security_settings') }}</legend>
 
-          <div class="form-group">
-            <label for="settingForm[security:registrationMode]" class="col-xs-3 control-label">{{ t('Basic authentication') }}</label>
-            <div class="col-xs-3">
-              <label for="">ID</label>
-              <input class="form-control" type="text" name="settingForm[security:basicName]" value="{{ settingForm['security:basicName']|default('') }}" autocomplete="nope" {% if not isAclEnabled  %}readonly{% endif%}>
-            </div>
-            <div class="col-xs-3">
-              <label for="">{{ t('Password') }}</label>
-              <input class="form-control" type="text" name="settingForm[security:basicSecret]" value="{{ settingForm['security:basicSecret']|default('') }}" autocomplete="nope" {% if not isAclEnabled  %}readonly{% endif%}>
-            </div>
-            <div class="col-xs-offset-3 col-xs-9">
-              <p class="help-block small">
-                {% if not isAclEnabled %}
-                  {{ t("security_setting.basic_acl_disable") }}<br>
-                {% else %}
-                  {{ t("security_setting.common_authentication") }}<br>
-                  {{ t("security_setting.without_encryption") }}<br>
-                {% endif %}
-              </p>
-            </div>
-          </div>
-
           <div class="form-group">
             <label for="settingForm[security:restrictGuestMode]" class="col-xs-3 control-label">{{ t('Guest users access') }}</label>
             <div class="col-xs-6">
-              <select class="form-control selectpicker" name="settingForm[security:restrictGuestMode]" value="{{ settingForm['security:restrictGuestMode'] }}">
+              <select class="form-control selectpicker" name="settingForm[security:restrictGuestMode]" value="{{ getConfig('crowi', 'security:restrictGuestMode') }}">
                 {% for modeValue, modeLabel in consts.restrictGuestMode %}
-                <option value="{{ t(modeValue) }}" {% if modeValue == settingForm['security:restrictGuestMode'] %}selected{% endif %} >{{ t(modeLabel) }}</option>
+                <option value="{{ t(modeValue) }}" {% if modeValue == getConfig('crowi', 'security:restrictGuestMode') %}selected{% endif %} >{{ t(modeLabel) }}</option>
                 {% endfor %}
               </select>
             </div>
@@ -76,9 +54,9 @@
           <div class="form-group">
             <label for="settingForm[security:registrationMode]" class="col-xs-3 control-label">{{ t('Register limitation') }}</label>
             <div class="col-xs-6">
-              <select class="form-control selectpicker" name="settingForm[security:registrationMode]" value="{{ settingForm['security:registrationMode'] }}">
+              <select class="form-control selectpicker" name="settingForm[security:registrationMode]" value="{{ getConfig('crowi', 'security:registrationMode') }}">
                 {% for modeValue, modeLabel in consts.registrationMode %}
-                <option value="{{ t(modeValue) }}" {% if modeValue == settingForm['security:registrationMode'] %}selected{% endif %} >{{ t(modeLabel) }}</option>
+                <option value="{{ t(modeValue) }}" {% if modeValue == getConfig('crowi', 'security:registrationMode') %}selected{% endif %} >{{ t(modeLabel) }}</option>
                 {% endfor %}
               </select>
               <p class="help-block small">{{ t('The contents entered here will be shown in the header etc') }}</p>
@@ -88,7 +66,7 @@
           <div class="form-group">
             <label for="settingForm[security:registrationWhiteList]" class="col-xs-3 control-label">{{ t('The whitelist of registration permission E-mail address') }}</label>
             <div class="col-xs-8">
-              <textarea class="form-control" type="textarea" name="settingForm[security:registrationWhiteList]" placeholder="{{ t('security_setting.example') }}: @growi.org">{{ settingForm['security:registrationWhiteList']|join('&#13')|raw }}</textarea>
+              <textarea class="form-control" type="textarea" name="settingForm[security:registrationWhiteList]" placeholder="{{ t('security_setting.example') }}: @growi.org">{{ getConfig('crowi', 'security:registrationWhiteList') | join('&#13') | raw }}</textarea>
               <p class="help-block small">{{ t("security_setting.restrict_emails") }}{{ t("security_setting.for_instance") }}<code>@growi.org</code>{{ t("security_setting.only_those") }}<br>
               {{ t("security_setting.insert_single") }}</p>
             </div>
@@ -96,7 +74,7 @@
 
           <div class="form-group">
             {% set configName = 'settingForm[security:list-policy:hideRestrictedByOwner]' %}
-            {% set configValue = settingForm['security:list-policy:hideRestrictedByOwner'] %}
+            {% set configValue = getConfig('crowi', 'security:list-policy:hideRestrictedByOwner') %}
             {% set isEnabled = !configValue %}
             <label for="{{configName}}" class="col-xs-3 control-label">{{ t("security_setting.page_listing_1") }}</label>
             <div class="col-xs-9">
@@ -117,7 +95,7 @@
 
           <div class="form-group">
             {% set configName = 'settingForm[security:list-policy:hideRestrictedByGroup]' %}
-            {% set configValue = settingForm['security:list-policy:hideRestrictedByGroup'] %}
+            {% set configValue = getConfig('crowi', 'security:list-policy:hideRestrictedByGroup') %}
             {% set isEnabled = !configValue %}
             <label for="{{configName}}" class="col-xs-3 control-label">{{ t("security_setting.page_listing_2") }}</label>
             <div class="col-xs-9">
@@ -173,139 +151,16 @@
         </div>
        </div>
 
-      <form action="/_api/admin/security/mechanism" method="post" class="form-horizontal mt-5" id="mechanismSetting" role="form">
-        <fieldset>
-          <legend class="alert-anchor">{{ t('Selecting authentication mechanism') }}</legend>
-          <p class="alert alert-info"><b>{{ t("security_setting.note") }}: </b>{{ t("security_setting.require_server_restart_change_auth") }}</p>
-          <div class="form-group">
-            <div class="col-xs-6">
-              <h4>
-                <div class="radio radio-primary">
-                  <input type="radio" id="radioPassportAuthMech" name="settingForm[security:isEnabledPassport]" value="true"
-                      {% if true === settingForm['security:isEnabledPassport'] %}checked="checked"{% endif %}>
-                  <label for="radioPassportAuthMech">
-                    <a href="http://passportjs.org/" target="_blank">
-                      <img src="/images/admin/security/passport-logo.svg" class="passport-logo"> Passport
-                    </a> {{ t("security_setting.auth_mechanism") }} <small class="text-success">({{ t("security_setting.recommended") }})</small>
-                  </label>
-                </div>
-              </h4>
-              <ul>
-                <li>{{ t("security_setting.username_email_password") }}</li>
-                <li>{{ t("security_setting.ldap_auth") }}</li>
-                <li>{{ t("security_setting.saml_auth") }}</li>
-                <li>{{ t("security_setting.google_auth2") }}</li>
-                <li>{{ t("security_setting.github_auth2") }}</li>
-                <li>{{ t("security_setting.twitter_auth2") }}</li>
-                <li class="text-muted">(TBD) <del>{{ t("security_setting.facebook_auth2") }}</del></li>
-              </ul>
-            </div>
-            <div class="col-xs-6">
-              <h4>
-                <div class="radio radio-primary">
-                  <input type="radio" id="radioCrowiAuthMech" name="settingForm[security:isEnabledPassport]" value="false"
-                      {% if !settingForm['security:isEnabledPassport'] %}checked="checked"{% endif %}>
-                  <label for="radioCrowiAuthMech">
-                    Crowi Classic {{ t("security_setting.auth_mechanism") }}
-                  </label>
-                </div>
-              </h4>
-              <ul>
-                <li>{{ t("security_setting.username_email_password") }}</li>
-                <li class="text-muted">
-                  {{ t("security_setting.google_auth2") }}
-                  <ul><li>{{ t("security_setting.google_auth2_by_crowi_desc") }}</li></ul>
-                </li>
-              </ul>
-            </div>
-          </div>
-
-          <div class="form-group">
-            <div class="col-xs-offset-5 col-xs-6">
-              <input type="hidden" name="_csrf" value="{{ csrf() }}">
-              <button type="submit" class="btn btn-primary">{{ t('Update') }}</button>
-            </div>
-          </div>
-        </fieldset>
-      </form>
-
 
       <div class="auth-mechanism-configurations m-t-10">
 
         <legend>{{ t('security_setting.Authentication mechanism settings') }}</legend>
 
-        {% set isOfficialConfigurationVisible = !isEnabledPassport() %}
-        <div class="official-crowi-auth-settings" {% if !isOfficialConfigurationVisible %}style="display: none;"{% endif %}>
-          {% set isRestartingServerNeeded = isPassportLocalStrategySetup() %}
-          <p class="alert alert-warning"
-              {% if !isRestartingServerNeeded %}style="display: none;"{% endif %}>
-            <b>
-              <i class="icon-exclamation" aria-hidden="true"></i>
-              {{ t("security_setting.require_server_restart") }}
-            </b>
-            {{ t("security_setting.server_on_passport_auth") }}
-          </p>
-
-          <form action="/_api/admin/security/google" method="post" class="form-horizontal" id="googleSetting" role="form"
-              {% if isRestartingServerNeeded %}style="opacity: 0.4;"{% endif %}>
-
-            <fieldset>
-              <h4>{{ t("security_setting.google_setting") }}</h4>
-              <p class="well alert-anchor">
-                {{ t("security_setting.connect_api_manager") }}
-              </p>
-
-              <ol class="help-block">
-                <li>{{ t("security_setting.access_api_manager", "https://console.cloud.google.com/apis/credentials", "API Manager") }}</li>
-                <li>{{ t("security_setting.create_project") }}</li>
-                <li>{{ t("security_setting.create_auth_to_oauth") }}</li>
-                <ol>
-                  <li>{{ t("security_setting.select_webapp") }}</li>
-                  <li>{{ t("security_setting.change_redirect_url") }}</li>
-                </ol>
-              </ol>
-
-              <div class="form-group">
-                <label for="settingForm[google:clientId]" class="col-xs-3 control-label">{{ t("security_setting.clientID") }}</label>
-                <div class="col-xs-6">
-                  <input class="form-control" type="text" name="settingForm[google:clientId]" value="{{ settingForm['google:clientId'] }}">
-                </div>
-              </div>
-
-              <div class="form-group">
-                <label for="settingForm[google:clientSecret]" class="col-xs-3 control-label">{{ t("security_setting.client_secret") }}</label>
-                <div class="col-xs-6">
-                  <input class="form-control" type="text" name="settingForm[google:clientSecret]" value="{{ settingForm['google:clientSecret'] }}">
-                </div>
-              </div>
-
-              <div class="form-group">
-                <div class="col-xs-offset-3 col-xs-6">
-                  <input type="hidden" name="_csrf" value="{{ csrf() }}">
-                  <button type="submit" class="btn btn-primary">{{ t('Update') }}</button>
-                </div>
-              </div>
-
-            </fieldset>
-          </form>
-        </div>
-
         {#
          # passport settings nav
          #}
-        {% set isPassportConfigurationVisible = settingForm['security:isEnabledPassport'] %}
-        <div class="passport-settings" {% if !isPassportConfigurationVisible %}style="display: none;"{% endif %}>
-
-          {% set isRestartingServerNeeded = !isPassportLocalStrategySetup() %}
-          <p class="alert alert-warning"
-              {% if !isRestartingServerNeeded %}style="display: none;"{% endif %}>
-            <b>
-              <i class="icon-exclamation" aria-hidden="true"></i>
-              {{ t("security_setting.require_server_restart") }}
-            </b>
-            {{ t("security_setting.server_on_crowi_auth") }}
-          </p>
-          <ul class="nav nav-tabs" role="tablist" {% if isRestartingServerNeeded %}style="opacity: 0.4;"{% endif %}>
+        <div class="passport-settings">
+          <ul class="nav nav-tabs" role="tablist">
             <li class="active">
               <a href="#passport-ldap" data-toggle="tab" role="tab"><i class="fa fa-sitemap"></i> LDAP</a>
             </li>
@@ -332,7 +187,7 @@
             </li>
           </ul>
 
-          <div class="tab-content p-t-10" {% if isRestartingServerNeeded %}style="opacity: 0.4;"{% endif %}>
+          <div class="tab-content p-t-10">
             <div id="passport-ldap" class="tab-pane active" role="tabpanel" >
               {% include './widget/passport/ldap.html' with { settingForm: settingForm } %}
             </div>
@@ -421,20 +276,6 @@
         return false;
       });
     });
-
-    // switch display according to on / off of radio buttons
-    $('input[name="settingForm[security:isEnabledPassport]"]:radio').change(function() {
-      const isEnabledPassport = ($(this).val() === "true");
-
-      if (isEnabledPassport) {
-        $('.official-crowi-auth-settings').hide(400);
-        $('.passport-settings').show(400);
-      }
-      else {
-        $('.official-crowi-auth-settings').show(400);
-        $('.passport-settings').hide(400);
-      }
-    });
   </script>
 </div>
 {% endblock content_main %}

+ 1 - 1
src/server/views/admin/user-group-detail.html

@@ -1,6 +1,6 @@
 {% extends '../layout/admin.html' %}
 
-{% block html_title %}{{ customTitle(t('UserGroup Management') + '/' + userGroup.name) | preventXss }}{% endblock %}
+{% block html_title %}{{ customizeService.generateCustomTitle(t('UserGroup Management') + '/' + userGroup.name) | preventXss }}{% endblock %}
 
 {% block content_header %}
 <div class="header-wrap">

+ 1 - 1
src/server/views/admin/user-groups.html

@@ -1,6 +1,6 @@
 {% extends '../layout/admin.html' %}
 
-{% block html_title %}{{ customTitle(t('UserGroup Management')) }}{% endblock %}
+{% block html_title %}{{ customizeService.generateCustomTitle(t('UserGroup Management')) }}{% endblock %}
 
 {% block content_header %}
 <div class="header-wrap">

+ 1 - 1
src/server/views/admin/users.html

@@ -1,6 +1,6 @@
 {% extends '../layout/admin.html' %}
 
-{% block html_title %}{{ customTitle(t('User_Management')) }}{% endblock %}
+{% block html_title %}{{ customizeService.generateCustomTitle(t('User_Management')) }}{% endblock %}
 
 {% block content_header %}
 <div class="header-wrap">

+ 5 - 6
src/server/views/admin/widget/passport/github.html

@@ -1,9 +1,8 @@
-<form action="/_api/admin/security/passport-github" method="post" class="form-horizontal passportStrategy" id="githubSetting" role="form"
-    {% if isRestartingServerNeeded %}style="opacity: 0.4;"{% endif %}>
+<form action="/_api/admin/security/passport-github" method="post" class="form-horizontal passportStrategy" id="githubSetting" role="form">
   <legend class="alert-anchor">{{ t("security_setting.OAuth.GitHub.name") }} {{ t("security_setting.configuration") }}</legend>
 
   {% set nameForIsGitHubEnabled = "settingForm[security:passport-github:isEnabled]" %}
-  {% set isGitHubEnabled = settingForm['security:passport-github:isEnabled'] %}
+  {% set isGitHubEnabled = getConfig('crowi', 'security:passport-github:isEnabled') %}
   {% set siteUrl = getConfig('crowi', 'app:siteUrl') || '[INVALID]' %}
   {% set callbackUrl = siteUrl + '/passport/github/callback' %}
 
@@ -27,7 +26,7 @@
     <div class="form-group">
       <label for="settingForm[security:passport-github:clientId]" class="col-xs-3 control-label">{{ t("security_setting.clientID") }}</label>
       <div class="col-xs-6">
-        <input class="form-control" type="text" name="settingForm[security:passport-github:clientId]" value="{{ settingForm['security:passport-github:clientId'] || '' }}">
+        <input class="form-control" type="text" name="settingForm[security:passport-github:clientId]" value="{{ getConfig('crowi', 'security:passport-github:clientId') | default('') }}">
         <p class="help-block">
           <small>
             {{ t("security_setting.Use env var if empty", "OAUTH_GITHUB_CLIENT_SECRET") }}
@@ -39,7 +38,7 @@
     <div class="form-group">
       <label for="settingForm[security:passport-github:clientSecret]" class="col-xs-3 control-label">{{ t("security_setting.client_secret") }}</label>
       <div class="col-xs-6">
-        <input class="form-control" type="text" name="settingForm[security:passport-github:clientSecret]" value="{{ settingForm['security:passport-github:clientSecret'] || '' }}">
+        <input class="form-control" type="text" name="settingForm[security:passport-github:clientSecret]" value="{{ getConfig('crowi', 'security:passport-github:clientSecret') | default('') }}">
         <p class="help-block">
           <small>
             {{ t("security_setting.Use env var if empty", "OAUTH_GITHUB_CLIENT_SECRET") }}
@@ -65,7 +64,7 @@
       <div class="col-xs-6 col-xs-offset-3">
         <div class="checkbox checkbox-info">
           <input type="checkbox" id="bindByUserName-GitHub" name="settingForm[security:passport-github:isSameUsernameTreatedAsIdenticalUser]" value="1"
-              {% if settingForm['security:passport-github:isSameUsernameTreatedAsIdenticalUser'] %}checked{% endif %} />
+              {% if getConfig('crowi', 'security:passport-github:isSameUsernameTreatedAsIdenticalUser') %}checked{% endif %} />
           <label for="bindByUserName-GitHub">
             {{ t("security_setting.Treat username matching as identical", "username") }}
           </label>

+ 5 - 6
src/server/views/admin/widget/passport/google-oauth.html

@@ -1,9 +1,8 @@
-<form action="/_api/admin/security/passport-google" method="post" class="form-horizontal passportStrategy" id="googleSetting" role="form"
-    {% if isRestartingServerNeeded %}style="opacity: 0.4;"{% endif %}>
+<form action="/_api/admin/security/passport-google" method="post" class="form-horizontal passportStrategy" id="googleSetting" role="form">
   <legend class="alert-anchor">{{ t("security_setting.OAuth.Google.name") }} {{ t("security_setting.configuration") }}</legend>
 
   {% set nameForIsGoogleEnabled = "settingForm[security:passport-google:isEnabled]" %}
-  {% set isGoogleEnabled = settingForm['security:passport-google:isEnabled'] %}
+  {% set isGoogleEnabled = getConfig('crowi', 'security:passport-google:isEnabled') | default('') %}
   {% set siteUrl = getConfig('crowi', 'app:siteUrl') || '[INVALID]' %}
   {% set callbackUrl = siteUrl + '/passport/google/callback' %}
 
@@ -27,7 +26,7 @@
     <div class="form-group">
       <label for="settingForm[security:passport-google:clientId]" class="col-xs-3 control-label">{{ t("security_setting.clientID") }}</label>
       <div class="col-xs-6">
-        <input class="form-control" type="text" name="settingForm[security:passport-google:clientId]" value="{{ settingForm['security:passport-google:clientId'] || '' }}">
+        <input class="form-control" type="text" name="settingForm[security:passport-google:clientId]" value="{{ getConfig('crowi', 'security:passport-google:clientId') | default('') }}">
         <p class="help-block">
           <small>
             {{ t("security_setting.Use env var if empty", "OAUTH_GOOGLE_CLIENT_ID") }}
@@ -39,7 +38,7 @@
     <div class="form-group">
       <label for="settingForm[security:passport-google:clientSecret]" class="col-xs-3 control-label">{{ t("security_setting.client_secret") }}</label>
       <div class="col-xs-6">
-        <input class="form-control" type="text" name="settingForm[security:passport-google:clientSecret]" value="{{ settingForm['security:passport-google:clientSecret'] || '' }}">
+        <input class="form-control" type="text" name="settingForm[security:passport-google:clientSecret]" value="{{ getConfig('crowi', 'security:passport-google:clientSecret') | default('') }}">
         <p class="help-block">
           <small>
             {{ t("security_setting.Use env var if empty", "OAUTH_GOOGLE_CLIENT_SECRET") }}
@@ -65,7 +64,7 @@
       <div class="col-xs-6 col-xs-offset-3">
         <div class="checkbox checkbox-info">
           <input type="checkbox" id="bindByUserName-Google" name="settingForm[security:passport-google:isSameUsernameTreatedAsIdenticalUser]" value="1"
-              {% if settingForm['security:passport-google:isSameUsernameTreatedAsIdenticalUser'] %}checked{% endif %} />
+              {% if getConfig('crowi', 'security:passport-google:isSameUsernameTreatedAsIdenticalUser') %}checked{% endif %} />
           <label for="bindByUserName-Google">
             {{ t("security_setting.Treat username matching as identical", "username") }}
           </label>

+ 13 - 13
src/server/views/admin/widget/passport/ldap.html

@@ -4,7 +4,7 @@
     <legend>LDAP {{ t("security_setting.configuration") }}</legend>
 
     {% set nameForIsLdapEnabled = "settingForm[security:passport-ldap:isEnabled]" %}
-    {% set isLdapEnabled = settingForm['security:passport-ldap:isEnabled'] %}
+    {% set isLdapEnabled = getConfig('crowi', 'security:passport-ldap:isEnabled') %}
     <div class="form-group">
       <label for="{{nameForIsLdapEnabled}}" class="col-xs-3 control-label">Use LDAP</label>
       <div class="col-xs-6">
@@ -27,7 +27,7 @@
         <label for="settingForm[security:passport-ldap:serverUrl]" class="col-xs-3 control-label">Server URL</label>
         <div class="col-xs-6">
           <input class="form-control" type="text"
-              name="settingForm[security:passport-ldap:serverUrl]" value="{{ settingForm['security:passport-ldap:serverUrl'] || '' }}">
+              name="settingForm[security:passport-ldap:serverUrl]" value="{{ getConfig('crowi', 'security:passport-ldap:serverUrl') | default('') }}">
           <p class="help-block">
             <small>
               {{ t("security_setting.ldap.server_url_detail") }}<br>
@@ -38,7 +38,7 @@
       </div>
 
       {% set nameForIsUserBind = "settingForm[security:passport-ldap:isUserBind]" %}
-      {% set isUserBind = settingForm['security:passport-ldap:isUserBind'] %}
+      {% set isUserBind = getConfig('crowi', 'security:passport-ldap:isUserBind') %}
       <div class="form-group">
         <label for="{{nameForIsUserBind}}" class="col-xs-3 control-label">{{ t("security_setting.ldap.bind_mode") }}</label>
         <div class="col-xs-6">
@@ -59,7 +59,7 @@
         <label for="settingForm[security:passport-ldap:bindDN]" class="col-xs-3 control-label">Bind DN</label>
         <div class="col-xs-6">
           <input class="form-control" type="text"
-              name="settingForm[security:passport-ldap:bindDN]" value="{{ settingForm['security:passport-ldap:bindDN'] || '' }}">
+              name="settingForm[security:passport-ldap:bindDN]" value="{{ getConfig('crowi', 'security:passport-ldap:bindDN') | default('') }}">
           <p class="help-block passport-ldap-managerbind" {% if isUserBind %}style="display: none;"{% endif %}>
             <small>
               {{ t("security_setting.ldap.bind_DN_manager_detail") }}<br>
@@ -82,7 +82,7 @@
         <label for="settingForm[security:passport-ldap:bindDNPassword]" class="col-xs-3 control-label">{{ t("security_setting.ldap.bind_DN_password") }}</label>
         <div class="col-xs-6">
           <input class="form-control passport-ldap-managerbind" type="text" {% if isUserBind %}style="display: none;"{% endif %}
-              name="settingForm[security:passport-ldap:bindDNPassword]" value="{{ settingForm['security:passport-ldap:bindDNPassword'] || '' }}">
+              name="settingForm[security:passport-ldap:bindDNPassword]" value="{{ getConfig('crowi', 'security:passport-ldap:bindDNPassword') | default('') }}">
           <p class="help-block passport-ldap-managerbind">
             <small>
               {{ t("security_setting.ldap.bind_DN_password_manager_detail") }}
@@ -100,7 +100,7 @@
         <label for="settingForm[security:passport-ldap:searchFilter]" class="col-xs-3 control-label">{{ t("security_setting.ldap.search_filter") }}</label>
         <div class="col-xs-6">
           <input class="form-control" type="text" placeholder="Default: (uid={% raw %}{{username}}{% endraw %})"
-              name="settingForm[security:passport-ldap:searchFilter]" value="{{ settingForm['security:passport-ldap:searchFilter'] || '' }}">
+              name="settingForm[security:passport-ldap:searchFilter]" value="{{ getConfig('crowi', 'security:passport-ldap:searchFilter') | default('') }}">
           <p class="help-block">
             <small>
               {{ t("security_setting.ldap.search_filter_detail1") }}<br>
@@ -123,7 +123,7 @@
         <label for="settingForm[security:passport-ldap:attrMapUsername]" class="col-xs-3 control-label">username</label>
         <div class="col-xs-6">
           <input class="form-control" type="text" placeholder="Default: uid"
-              name="settingForm[security:passport-ldap:attrMapUsername]" value="{{ settingForm['security:passport-ldap:attrMapUsername'] || '' }}">
+              name="settingForm[security:passport-ldap:attrMapUsername]" value="{{ getConfig('crowi', 'security:passport-ldap:attrMapUsername') | default('') }}">
           <p class="help-block">
             <small>
               {{ t("security_setting.ldap.username_detail") }}
@@ -136,7 +136,7 @@
         <div class="col-xs-6 col-xs-offset-3">
           <div class="checkbox checkbox-info">
             <input type="checkbox" id="cbSameUsernameTreatedAsIdenticalUser" name="settingForm[security:passport-ldap:isSameUsernameTreatedAsIdenticalUser]" value="1"
-                {% if settingForm['security:passport-ldap:isSameUsernameTreatedAsIdenticalUser'] %}checked{% endif %} />
+                {% if getConfig('crowi', 'security:passport-ldap:isSameUsernameTreatedAsIdenticalUser') %}checked{% endif %} />
             <label for="cbSameUsernameTreatedAsIdenticalUser">
               {{ t("security_setting.Treat username matching as identical", "username") }}
             </label>
@@ -153,7 +153,7 @@
         <label for="settingForm[security:passport-ldap:attrMapMail]" class="col-xs-3 control-label">Mail</label>
         <div class="col-xs-6">
           <input class="form-control" type="text" placeholder="Default: mail"
-              name="settingForm[security:passport-ldap:attrMapMail]" value="{{ settingForm['security:passport-ldap:attrMapMail'] || '' }}">
+              name="settingForm[security:passport-ldap:attrMapMail]" value="{{ getConfig('crowi', 'security:passport-ldap:attrMapMail') | default('') }}">
           <p class="help-block">
             <small>
               {{ t("security_setting.ldap.mail_detail") }}
@@ -166,7 +166,7 @@
         <label for="settingForm[security:passport-ldap:attrMapName]" class="col-xs-3 control-label">Name</label>
         <div class="col-xs-6">
           <input class="form-control" type="text"
-              name="settingForm[security:passport-ldap:attrMapName]" value="{{ settingForm['security:passport-ldap:attrMapName'] || '' }}">
+              name="settingForm[security:passport-ldap:attrMapName]" value="{{ getConfig('crowi', 'security:passport-ldap:attrMapName') | default('') }}">
           <p class="help-block">
             <small>
               {{ t("security_setting.ldap.name_detail") }}
@@ -181,7 +181,7 @@
         <label for="settingForm[security:passport-ldap:groupSearchBase]" class="col-xs-3 control-label">{{ t("security_setting.ldap.group_search_base_DN") }}</label>
         <div class="col-xs-6">
           <input class="form-control" type="text"
-              name="settingForm[security:passport-ldap:groupSearchBase]" value="{{ settingForm['security:passport-ldap:groupSearchBase'] || '' }}">
+              name="settingForm[security:passport-ldap:groupSearchBase]" value="{{ getConfig('crowi', 'security:passport-ldap:groupSearchBase') | default('') }}">
           <p class="help-block">
             <small>
               {{ t("security_setting.ldap.group_search_base_DN_detail") }}<br>
@@ -195,7 +195,7 @@
         <label for="settingForm[security:passport-ldap:groupSearchFilter]" class="col-xs-3 control-label">{{ t("security_setting.ldap.group_search_filter") }}</label>
         <div class="col-xs-6">
           <input class="form-control" type="text"
-              name="settingForm[security:passport-ldap:groupSearchFilter]" value="{{ settingForm['security:passport-ldap:groupSearchFilter'] || '' }}">
+              name="settingForm[security:passport-ldap:groupSearchFilter]" value="{{ getConfig('crowi', 'security:passport-ldap:groupSearchFilter') | default('') }}">
           <p class="help-block">
             <small>
               {{ t("security_setting.ldap.group_search_filter_detail1") }}<br>
@@ -214,7 +214,7 @@
         <label for="settingForm[security:passport-ldap:groupSearchFilter]" class="col-xs-3 control-label">{{ t("security_setting.ldap.group_search_user_DN_property") }}</label>
         <div class="col-xs-6">
           <input class="form-control" type="text" placeholder="Default: uid"
-              name="settingForm[security:passport-ldap:groupDnProperty]" value="{{ settingForm['security:passport-ldap:groupDnProperty'] || '' }}">
+              name="settingForm[security:passport-ldap:groupDnProperty]" value="{{ getConfig('crowi', 'security:passport-ldap:groupDnProperty') | default('') }}">
           <p class="help-block">
             <small>
               {{ t("security_setting.ldap.group_search_user_DN_property_detail") }}

+ 12 - 13
src/server/views/admin/widget/passport/oidc.html

@@ -1,9 +1,8 @@
-<form action="/_api/admin/security/passport-oidc" method="post" class="form-horizontal passportStrategy" id="oidcSetting" role="form"
-    {% if isRestartingServerNeeded %}style="opacity: 0.4;"{% endif %}>
+<form action="/_api/admin/security/passport-oidc" method="post" class="form-horizontal passportStrategy" id="oidcSetting" role="form">
   <legend class="alert-anchor">{{ t("security_setting.OAuth.OIDC.name") }} {{ t("security_setting.configuration") }}</legend>
 
   {% set nameForIsOIDCEnabled = "settingForm[security:passport-oidc:isEnabled]" %}
-  {% set isOidcEnabled = settingForm['security:passport-oidc:isEnabled'] %}
+  {% set isOidcEnabled = getConfig('crowi', 'security:passport-oidc:isEnabled') %}
   {% set siteUrl = getConfig('crowi', 'app:siteUrl') || '[INVALID]' %}
   {% set callbackUrl = siteUrl + '/passport/oidc/callback' %}
 
@@ -27,14 +26,14 @@
     <div class="form-group">
       <label for="settingForm[security:passport-oidc:providerName]" class="col-xs-3 control-label">{{ t("security_setting.providerName") }}</label>
       <div class="col-xs-6">
-        <input class="form-control" type="text" name="settingForm[security:passport-oidc:providerName]" value="{{ settingForm['security:passport-oidc:providerName'] || '' }}">
+        <input class="form-control" type="text" name="settingForm[security:passport-oidc:providerName]" value="{{ getConfig('crowi', 'security:passport-oidc:providerName') | default('') }}">
       </div>
     </div>
 
     <div class="form-group">
       <label for="settingForm[security:passport-oidc:issuerHost]" class="col-xs-3 control-label">{{ t("security_setting.issuerHost") }}</label>
       <div class="col-xs-6">
-        <input class="form-control" type="text" name="settingForm[security:passport-oidc:issuerHost]" value="{{ settingForm['security:passport-oidc:issuerHost'] || '' }}">
+        <input class="form-control" type="text" name="settingForm[security:passport-oidc:issuerHost]" value="{{ getConfig('crowi', 'security:passport-oidc:issuerHost') | default('') }}">
         <p class="help-block">
           <small>
                 {{ t("security_setting.Use env var if empty", "OAUTH_OIDC_ISSUER_HOST") }}
@@ -46,7 +45,7 @@
     <div class="form-group">
       <label for="settingForm[security:passport-oidc:clientId]" class="col-xs-3 control-label">{{ t("security_setting.clientID") }}</label>
       <div class="col-xs-6">
-        <input class="form-control" type="text" name="settingForm[security:passport-oidc:clientId]" value="{{ settingForm['security:passport-oidc:clientId'] || '' }}">
+        <input class="form-control" type="text" name="settingForm[security:passport-oidc:clientId]" value="{{ getConfig('crowi', 'security:passport-oidc:clientId') | default('') }}">
         <p class="help-block">
           <small>
              {{ t("security_setting.Use env var if empty", "OAUTH_OIDC_CLIENT_ID") }}
@@ -58,7 +57,7 @@
     <div class="form-group">
       <label for="settingForm[security:passport-oidc:clientSecret]" class="col-xs-3 control-label">{{ t("security_setting.client_secret") }}</label>
       <div class="col-xs-6">
-        <input class="form-control" type="text" name="settingForm[security:passport-oidc:clientSecret]" value="{{ settingForm['security:passport-oidc:clientSecret'] || '' }}">
+        <input class="form-control" type="text" name="settingForm[security:passport-oidc:clientSecret]" value="{{ getConfig('crowi', 'security:passport-oidc:clientSecret') | default('') }}">
         <p class="help-block">
           <small>
              {{ t("security_setting.Use env var if empty", "OAUTH_OIDC_CLIENT_SECRET") }}
@@ -72,7 +71,7 @@
     <div class="form-group">
       <label for="settingForm[security:passport-oidc:attrMapId]" class="col-xs-3 control-label">Identifier</label>
       <div class="col-xs-6">
-        <input class="form-control" type="text" name="settingForm[security:passport-oidc:attrMapId]" value="{{ settingForm['security:passport-oidc:attrMapId'] || '' }}">
+        <input class="form-control" type="text" name="settingForm[security:passport-oidc:attrMapId]" value="{{ getConfig('crowi', 'security:passport-oidc:attrMapId') | default('') }}">
         <p class="help-block">
           <small>
             {{ t("security_setting.OAuth.OIDC.id_detail") }}
@@ -84,7 +83,7 @@
     <div class="form-group">
       <label for="settingForm[security:passport-oidc:attrMapUserName]" class="col-xs-3 control-label">Username</label>
       <div class="col-xs-6">
-        <input class="form-control" type="text" name="settingForm[security:passport-oidc:attrMapUserName]" value="{{ settingForm['security:passport-oidc:attrMapUserName'] || '' }}">
+        <input class="form-control" type="text" name="settingForm[security:passport-oidc:attrMapUserName]" value="{{ getConfig('crowi', 'security:passport-oidc:attrMapUserName') | default('') }}">
         <p class="help-block">
           <small>
             {{ t("security_setting.OAuth.OIDC.username_detail") }}
@@ -96,7 +95,7 @@
     <div class="form-group">
       <label for="settingForm[security:passport-oidc:attrMapName]" class="col-xs-3 control-label">Name</label>
       <div class="col-xs-6">
-        <input class="form-control" type="text" name="settingForm[security:passport-oidc:attrMapName]" value="{{ settingForm['security:passport-oidc:attrMapName'] || '' }}">
+        <input class="form-control" type="text" name="settingForm[security:passport-oidc:attrMapName]" value="{{ getConfig('crowi', 'security:passport-oidc:attrMapName') | default('') }}">
         <p class="help-block">
           <small>
             {{ t("security_setting.OAuth.OIDC.name_detail") }}
@@ -108,7 +107,7 @@
     <div class="form-group">
       <label for="settingForm[security:passport-oidc:attrMapMail]" class="col-xs-3 control-label">Mail</label>
       <div class="col-xs-6">
-        <input class="form-control" type="text" name="settingForm[security:passport-oidc:attrMapMail]" value="{{ settingForm['security:passport-oidc:attrMapMail'] || '' }}">
+        <input class="form-control" type="text" name="settingForm[security:passport-oidc:attrMapMail]" value="{{ getConfig('crowi', 'security:passport-oidc:attrMapMail') | default('') }}">
         <p class="help-block">
           <small>
             {{ t("security_setting.OAuth.OIDC.mapping_detail", t("Email")) }}
@@ -134,7 +133,7 @@
       <div class="col-xs-6 col-xs-offset-3">
         <div class="checkbox checkbox-info">
           <input type="checkbox" id="bindByUserName-oidc" name="settingForm[security:passport-oidc:isSameUsernameTreatedAsIdenticalUser]" value="1"
-              {% if settingForm['security:passport-oidc:isSameUsernameTreatedAsIdenticalUser'] %}checked{% endif %} />
+              {% if getConfig('crowi', 'security:passport-oidc:isSameUsernameTreatedAsIdenticalUser') %}checked{% endif %} />
           <label for="bindByUserName-oidc">
             {{ t("security_setting.Treat username matching as identical", "username") }}
           </label>
@@ -151,7 +150,7 @@
       <div class="col-xs-6 col-xs-offset-3">
         <div class="checkbox checkbox-info">
           <input type="checkbox" id="bindByEmail-oidc" name="settingForm[security:passport-oidc:isSameEmailTreatedAsIdenticalUser]" value="1"
-              {% if settingForm['security:passport-oidc:isSameEmailTreatedAsIdenticalUser'] %}checked{% endif %} />
+              {% if getConfig('crowi', 'security:passport-oidc:isSameEmailTreatedAsIdenticalUser') %}checked{% endif %} />
           <label for="bindByEmail-oidc">
             {{ t("security_setting.Treat email matching as identical", "email") }}
           </label>

+ 1 - 2
src/server/views/admin/widget/passport/saml.html

@@ -1,5 +1,4 @@
-<form action="/_api/admin/security/passport-saml" method="post" class="form-horizontal passportStrategy" id="samlSetting" role="form"
-    {% if isRestartingServerNeeded %}style="opacity: 0.4;"{% endif %}>
+<form action="/_api/admin/security/passport-saml" method="post" class="form-horizontal passportStrategy" id="samlSetting" role="form">
   <legend class="alert-anchor">{{ t("security_setting.SAML.name") }} {{ t("security_setting.configuration") }}</legend>
 
   {% set nameForIsSamlEnabled = "settingForm[security:passport-saml:isEnabled]" %}

+ 5 - 6
src/server/views/admin/widget/passport/twitter.html

@@ -1,9 +1,8 @@
-<form action="/_api/admin/security/passport-twitter" method="post" class="form-horizontal passportStrategy" id="twitterSetting" role="form"
-    {% if isRestartingServerNeeded %}style="opacity: 0.4;"{% endif %}>
+<form action="/_api/admin/security/passport-twitter" method="post" class="form-horizontal passportStrategy" id="twitterSetting" role="form">
   <legend class="alert-anchor">{{ t("security_setting.OAuth.Twitter.name") }} {{ t("security_setting.configuration") }}</legend>
 
   {% set nameForIsTwitterEnabled = "settingForm[security:passport-twitter:isEnabled]" %}
-  {% set isTwitterEnabled = settingForm['security:passport-twitter:isEnabled'] %}
+  {% set isTwitterEnabled = getConfig('crowi', 'security:passport-twitter:isEnabled') %}
   {% set siteUrl = getConfig('crowi', 'app:siteUrl') || '[INVALID]' %}
   {% set callbackUrl = siteUrl + '/passport/twitter/callback' %}
 
@@ -29,7 +28,7 @@
     <div class="form-group">
       <label for="settingForm[security:passport-twitter:consumerKey]" class="col-xs-3 control-label">{{ t("security_setting.clientID") }}</label>
       <div class="col-xs-6">
-        <input class="form-control" type="text" name="settingForm[security:passport-twitter:consumerKey]" value="{{ settingForm['security:passport-twitter:consumerKey'] || '' }}">
+        <input class="form-control" type="text" name="settingForm[security:passport-twitter:consumerKey]" value="{{ getConfig('crowi', 'security:passport-twitter:consumerKey') | default('') }}">
         <p class="help-block">
           <small>
                 {{ t("security_setting.Use env var if empty", "OAUTH_TWITTER_CONSUMER_KEY") }}
@@ -41,7 +40,7 @@
     <div class="form-group">
       <label for="settingForm[security:passport-twitter:consumerSecret]" class="col-xs-3 control-label">{{ t("security_setting.client_secret") }}</label>
       <div class="col-xs-6">
-        <input class="form-control" type="text" name="settingForm[security:passport-twitter:consumerSecret]" value="{{ settingForm['security:passport-twitter:consumerSecret'] || '' }}">
+        <input class="form-control" type="text" name="settingForm[security:passport-twitter:consumerSecret]" value="{{ getConfig('crowi', 'security:passport-twitter:consumerSecret') | default('') }}">
         <p class="help-block">
           <small>
              {{ t("security_setting.Use env var if empty", "OAUTH_TWITTER_CONSUMER_SECRET") }}
@@ -68,7 +67,7 @@
       <div class="col-xs-6 col-xs-offset-3">
         <div class="checkbox checkbox-info">
           <input type="checkbox" id="bindByUserName-Twitter" name="settingForm[security:passport-twitter:isSameUsernameTreatedAsIdenticalUser]" value="1"
-              {% if settingForm['security:passport-twitter:isSameUsernameTreatedAsIdenticalUser'] %}checked{% endif %} />
+              {% if getConfig('crowi', 'security:passport-twitter:isSameUsernameTreatedAsIdenticalUser') %}checked{% endif %} />
           <label for="bindByUserName-Twitter">
             {{ t("security_setting.Treat username matching as identical", "username") }}
           </label>

+ 2 - 2
src/server/views/customlayout-selector/forbidden.html

@@ -1,6 +1,6 @@
-{% if !layoutType() || 'crowi' === layoutType() %}
+{% if !getConfig('crowi', 'customize:layout') || 'crowi' === getConfig('crowi', 'customize:layout') %}
   {% include '../layout-crowi/forbidden.html' %}
-{% elseif !layoutType() || 'kibela' === layoutType()%}
+{% elseif !getConfig('crowi', 'customize:layout') || 'kibela' === getConfig('crowi', 'customize:layout')%}
   {% include '../layout-kibela/forbidden.html' %}
 {% else %}
   {% include '../layout-growi/forbidden.html' %}

+ 2 - 2
src/server/views/customlayout-selector/not_creatable.html

@@ -1,6 +1,6 @@
-{% if !layoutType() || 'crowi' === layoutType() %}
+{% if !getConfig('crowi', 'customize:layout') || 'crowi' === getConfig('crowi', 'customize:layout') %}
   {% include '../layout-crowi/not_creatable.html' %}
-{% elseif !layoutType() || 'kibela' === layoutType()%}
+{% elseif !getConfig('crowi', 'customize:layout') || 'kibela' === getConfig('crowi', 'customize:layout')%}
   {% include '../layout-kibela/not_creatable.html' %}
 {% else %}
   {% include '../layout-growi/not_creatable.html' %}

+ 2 - 2
src/server/views/customlayout-selector/not_found.html

@@ -1,6 +1,6 @@
-{% if !layoutType() || 'crowi' === layoutType() %}
+{% if !getConfig('crowi', 'customize:layout') || 'crowi' === getConfig('crowi', 'customize:layout') %}
   {% include '../layout-crowi/not_found.html' %}
-{% elseif !layoutType() || 'kibela' === layoutType()%}
+{% elseif !getConfig('crowi', 'customize:layout') || 'kibela' === getConfig('crowi', 'customize:layout')%}
   {% include '../layout-kibela/not_found.html' %}
 {% else %}
   {% include '../layout-growi/not_found.html' %}

+ 2 - 2
src/server/views/customlayout-selector/page.html

@@ -1,6 +1,6 @@
-{% if !layoutType() || 'crowi' === layoutType() %}
+{% if !getConfig('crowi', 'customize:layout') || 'crowi' === getConfig('crowi', 'customize:layout') %}
   {% include '../layout-crowi/page.html' %}
-{% elseif !layoutType() || 'kibela' === layoutType()%}
+{% elseif !getConfig('crowi', 'customize:layout') || 'kibela' === getConfig('crowi', 'customize:layout')%}
   {% include '../layout-kibela/page.html' %}
 {% else %}
   {% include '../layout-growi/page.html' %}

+ 2 - 2
src/server/views/customlayout-selector/page_list.html

@@ -1,6 +1,6 @@
-{% if !layoutType() || 'crowi' === layoutType() %}
+{% if !getConfig('crowi', 'customize:layout') || 'crowi' === getConfig('crowi', 'customize:layout') %}
   {% include '../layout-crowi/page_list.html' %}
-{% elseif !layoutType() || 'kibela' === layoutType()%}
+{% elseif !getConfig('crowi', 'customize:layout') || 'kibela' === getConfig('crowi', 'customize:layout')%}
   {% include '../layout-kibela/page_list.html' %}
 {% else %}
   {% include '../layout-growi/page_list.html' %}

+ 2 - 2
src/server/views/customlayout-selector/user_page.html

@@ -1,6 +1,6 @@
-{% if !layoutType() || 'crowi' === layoutType() %}
+{% if !getConfig('crowi', 'customize:layout') || 'crowi' === getConfig('crowi', 'customize:layout') %}
   {% include '../layout-crowi/user_page.html' %}
-{% elseif !layoutType() || 'kibela' === layoutType()%}
+{% elseif !getConfig('crowi', 'customize:layout') || 'kibela' === getConfig('crowi', 'customize:layout')%}
   {% include '../layout-kibela/user_page.html' %}
 {% else %}
   {% include '../layout-growi/user_page.html' %}

+ 75 - 41
src/server/views/installer.html

@@ -1,59 +1,93 @@
-{% extends 'layout/layout.html' %}
+<!DOCTYPE html>
+<html>
+{% block html_head %}
+<head>
+  <meta charset="utf-8">
+  <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+  <title>{{ customizeService.generateCustomTitle(t('installer.setup')) }}</title>
+  <meta name="description" content="">
+  <meta name="author" content="">
 
-{% block html_base_css %}installer nologin{% endblock %}
+  <meta name="viewport" content="width=device-width,initial-scale=1">
 
-{% block html_title %}{{ customTitle(t('installer.setup')) }}{% endblock %}
+  <meta name="apple-mobile-web-app-title" content="{{ appService.getAppTitle() }}">
 
-{#
- # Remove default contents
- #}
-{% block html_head_loading_legacy %}
-{% endblock %}
-{% block html_head_loading_app %}
-{% endblock %}
-{% block layout_head_nav %}
-{% endblock %}
-{% block sidebar %}
-{% endblock %}
+  {% include './widget/headers/favicon.html' %}
+  {% include './widget/headers/ie11-polyfills.html' %}
+
+  {{ cdnScriptTagsByGroup('basis') }}
+
+  {% include './widget/headers/scripts-for-dev.html' %}
+
+  <script src="{{ webpack_asset('js/vendors.js') }}" defer></script>
+  <script src="{{ webpack_asset('js/commons.js') }}" defer></script>
+
+  <!-- styles -->
+  {% include './widget/headers/styles-for-app.html' %}
+  {% block theme_css_block %}
+    {% include './widget/headers/styles-theme.html' with {theme: 'default'} %}
+  {% endblock %}
+
+  {{ cdnStyleTagsByGroup('basis') }}
 
-{% block html_additional_headers %}
-  {% parent %}
   <script src="{{ webpack_asset('js/installer.js') }}" defer></script>
+
+</head>
 {% endblock %}
 
-{% block layout_main %}
+{% block html_body %}
+<body
+  class="main-container content-wrapper installer nologin growi"
+  {% block html_base_attr %}{% endblock %}
+  data-csrftoken="{{ csrf() }}"
+ >
+
+<div id="wrapper">
 
-<div class="main container-fluid">
+  <!-- Page Content -->
+  <div id="page-wrapper">
+    <div class="main container-fluid">
 
-  <div class="row">
+      <div class="row">
 
-    <div class="login-header col-sm-offset-4 col-sm-4">
-      <div class="logo">{% include 'widget/logo.html' %}</div>
-      <h1>GROWI</h1>
+        <div class="login-header col-sm-offset-4 col-sm-4">
+          <div class="logo">{% include 'widget/logo.html' %}</div>
+          <h1>GROWI</h1>
 
-      <div class="login-form-errors">
-        {% if req.form.errors.length > 0 %}
-        <div class="alert alert-danger">
-          <ul>
-          {% for error in req.form.errors %}
-            <li>{{ error }}</li>
-          {% endfor %}
-          </ul>
+          <div class="login-form-errors">
+            {% if req.form.errors.length > 0 %}
+            <div class="alert alert-danger">
+              <ul>
+              {% for error in req.form.errors %}
+                <li>{{ error }}</li>
+              {% endfor %}
+              </ul>
+            </div>
+            {% endif %}
+          </div>
         </div>
-        {% endif %}
-      </div>
-    </div>
 
-    <div id='installer-form'
-      data-user-name="{{ req.body.registerForm.username }}"
-      data-name="{{ googleName|default(req.body.registerForm.name) }}"
-      data-email="{{ googleEmail|default(req.body.registerForm.email) }}"
-      data-csrf="{{ csrf() }}">
-    </div>
+        <div id='installer-form'
+          data-user-name="{{ req.body.registerForm.username }}"
+          data-name="{{ req.body.registerForm.name }}"
+          data-email="{{ req.body.registerForm.email }}"
+          data-csrf="{{ csrf() }}">
+        </div>
 
-  </div>{# /.row #}
+      </div>{# /.row #}
 
-</div>{# /.main #}
+    </div>
+  </div><!-- /#page-wrapper -->
+
+</div><!-- /#wrapper -->
 
+{% block body_end %}
 {% endblock %}
+</body>
+{% endblock %}
+
+<script type="application/json" id="crowi-context-hydrate">
+{{ local_config|json|safe|preventXss }}
+</script>
 
+</html>

+ 2 - 2
src/server/views/invited.html

@@ -2,7 +2,7 @@
 
 {% block html_base_css %}invited nologin{% endblock %}
 
-{% block html_title %}{{ customTitle('Registration') }}{% endblock %}
+{% block html_title %}{{ customizeService.generateCustomTitle('Registration') }}{% endblock %}
 
 
 
@@ -73,7 +73,7 @@
 
         <div class="input-group">
           <span class="input-group-addon"><i class="icon-tag"></i></span>
-          <input type="text" class="form-control" placeholder="{{ t('Name') }}" name="invitedForm[name]" value="{{ googleName|default(req.body.invitedForm.name) }}" required>
+          <input type="text" class="form-control" placeholder="{{ t('Name') }}" name="invitedForm[name]" value="{{ req.body.invitedForm.name }}" required>
         </div>
 
 

+ 1 - 1
src/server/views/layout-crowi/base/layout.html

@@ -1,6 +1,6 @@
 {% extends '../../layout/layout.html' %}
 
-{% block html_title %}{{ customTitle(path) }}{% endblock %}
+{% block html_title %}{{ customizeService.generateCustomTitle(path) }}{% endblock %}
 
 {% block html_additional_headers %}
   {% parent %}

+ 1 - 1
src/server/views/layout-growi/page.html

@@ -34,7 +34,7 @@
 
   </div>
 
-  {% if 'growi' === behaviorType() || 'crowi-plus' === behaviorType() %}
+  {% if 'growi' === getConfig('crowi', 'customize:behavior') || 'crowi-plus' === getConfig('crowi', 'customize:behavior') %}
   <div class="row page-list hidden-print m-t-30">
     <div class="col-md-10">
       {% include '../widget/page_list_and_timeline.html' %}

+ 1 - 1
src/server/views/layout-growi/user_page.html

@@ -58,7 +58,7 @@
 
   </div>
 
-  {% if 'growi' === behaviorType() || 'crowi-plus' === behaviorType() %}
+  {% if 'growi' === getConfig('crowi', 'customize:behavior') || 'crowi-plus' === getConfig('crowi', 'customize:behavior') %}
   <div class="row page-list hidden-print m-t-30">
     <div class="col-md-10">
       {% include '../widget/page_list_and_timeline.html' %}

+ 1 - 1
src/server/views/layout-growi/widget/header.html

@@ -41,7 +41,7 @@
       </ul>
       {% endif %}
 
-      {% if not page and not forbidden and ('/' === path or 'crowi' === behaviorType()) and not isUserPageList(path) and !isTrashPage() %}
+      {% if not page and not forbidden and ('/' === path or 'crowi' === getConfig('crowi', 'customize:behavior')) and not isUserPageList(path) and !isTrashPage() %}
         {% if '/' === path.slice(-1) %}
           {% include '../../widget/create_portal.html' %}
         {% endif %}

+ 1 - 1
src/server/views/layout-kibela/page.html

@@ -30,7 +30,7 @@
 
 </div>
 
-  {% if 'growi' === behaviorType() || 'crowi-plus' === behaviorType() %}
+  {% if 'growi' === getConfig('crowi', 'customize:behavior') || 'crowi-plus' === getConfig('crowi', 'customize:behavior') %}
   <div class="row page-list p-t-10 m-t-30 m-b-30 round-corner">
     <div class="col-md-10">
       {% include '../widget/page_list_and_timeline.html' %}

+ 1 - 1
src/server/views/layout-kibela/user_page.html

@@ -51,7 +51,7 @@
 
   </div>
 
-  {% if 'growi' === behaviorType() || 'crowi-plus' === behaviorType() %}
+  {% if 'growi' === getConfig('crowi', 'customize:behavior') || 'crowi-plus' === getConfig('crowi', 'customize:behavior') %}
   <div class="row page-list m-t-30">
     <div class="col-xs-12">
       {% include '../widget/page_list_and_timeline_kibela.html' %}

+ 1 - 1
src/server/views/layout-kibela/widget/header.html

@@ -41,7 +41,7 @@
         </div>
       </li>
     </ul>
-    {% endif %} {% if not page and ('/' === path or 'crowi' === behaviorType()) and not isUserPageList(path) and !isTrashPage()
+    {% endif %} {% if not page and ('/' === path or 'crowi' === getConfig('crowi', 'customize:behavior')) and not isUserPageList(path) and !isTrashPage()
     %} {% if '/' === path.slice(-1) %} {% include '../../widget/create_portal.html' %} {% endif %} {% endif %}
 
   </div>

+ 27 - 74
src/server/views/layout/layout.html

@@ -4,104 +4,57 @@
 <head>
   <meta charset="utf-8">
   <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
-  <title>{% block html_title %}{{ customTitle(path) }}{% endblock %}</title>
+  <title>{% block html_title %}{{ customizeService.generateCustomTitle(path) }}{% endblock %}</title>
   <meta name="description" content="">
   <meta name="author" content="">
 
   <meta name="viewport" content="width=device-width,initial-scale=1">
 
-  <meta name="apple-mobile-web-app-title" content="{{ appTitle() }}">
+  <meta name="apple-mobile-web-app-title" content="{{ appService.getAppTitle() }}">
 
-  {% include '../widget/favicon.html' %}
+  {{ getConfig('crowi', 'customize:header') | default('') }}
 
-  {{ customHeader() }}
-
-  <!-- polyfills for IE11 -->
-  <script>
-    var userAgent = window.navigator.userAgent.toLowerCase();
-    if (userAgent.indexOf('msie') != -1 || userAgent.indexOf('trident') != -1) {
-      var scriptElement = document.createElement('script');
-      scriptElement.src = '{{ webpack_asset("js/ie11-polyfill.js") }}';
-      var headElement = document.getElementsByTagName('head')[0];
-      headElement.appendChild(scriptElement);
-    }
-  </script>
+  {% include '../widget/headers/favicon.html' %}
+  {% include '../widget/headers/ie11-polyfills.html' %}
 
   {{ cdnScriptTagsByGroup('basis') }}
 
   {% if local_config.env.MATHJAX %}
-    <!-- Mathjax -->
-    <script type="text/x-mathjax-config" async>
-      MathJax.Hub.Config({
-        skipStartupTypeset: true,
-        extensions: ["tex2jax.js"],
-        jax: ["input/TeX", "output/SVG"],
-        tex2jax: {
-          processEscapes: true
-        },
-        showMathMenu: false,
-        showMathMenuMSIE: false,
-        showProcessingMessages: false,
-        messageStyle: "none"
-      });
-    </script>
-    {{ cdnScriptTag('mathjax') }}
+    {% include '../widget/headers/mathjax.html' %}
   {% endif %}
 
-  {% if env === 'development' %}
-    <script src="/dll/dll.js"></script>
-    <script src="{{ webpack_asset('js/dev.js') }}" async></script>
-    <!-- Browsersync -->
-    <script id="__bs_script__">//<![CDATA[
-      document.write("<script async src='http://HOST:3001/browser-sync/browser-sync-client.js?v=2.23.6'><\/script>".replace("HOST", location.hostname));
-    //]]></script>
-  {% endif %}
+  {% include '../widget/headers/scripts-for-dev.html' %}
 
   <script src="{{ webpack_asset('js/vendors.js') }}" defer></script>
   <script src="{{ webpack_asset('js/commons.js') }}" defer></script>
-  {% if isEnabledPlugins() %}
+  {% if getConfig('crowi', 'plugin:isEnabledPlugins') %}
   <script src="{{ webpack_asset('js/plugin.js') }}" defer></script>
   {% endif %}
   {% block html_head_loading_legacy %}
-  <script src="{{ webpack_asset('js/legacy.js') }}" defer></script>
+    <script src="{{ webpack_asset('js/legacy.js') }}" defer></script>
   {% endblock %}
   {% block html_head_loading_app %}
-  <script src="{{ webpack_asset('js/app.js') }}" defer></script>
+    <script src="{{ webpack_asset('js/app.js') }}" defer></script>
   {% endblock %}
 
   <!-- styles -->
-  {% block style_css_block %}
-    {% if env === 'development' %}
-      <script src="{{ webpack_asset('styles/style-commons.js') }}"></script>
-      <script src="{{ webpack_asset('styles/style-app.js') }}"></script>
-    {% else %}
-      <script src="{{ webpack_asset('styles/style-commons.js') }}"></script>
-      <link rel="stylesheet" href="{{ webpack_asset('styles/style-app.css') }}">
-    {% endif %}
-  {% endblock %}
-  {% block theme_css_block %}
-    {% if 'kibela' === layoutType() %}
-      {% if env === 'development' %}
-        <script src="{{ webpack_asset('styles/theme-kibela.js') }}"></script>
-      {% else %}
-        <link rel="stylesheet" href="{{ webpack_asset('styles/theme-kibela.css') }}">
-      {% endif %}
-    {% else %}
-      {% if env === 'development' %}
-        <script src="{{ webpack_asset('styles/theme-' + theme() + '.js') }}"></script>
-      {% else %}
-        <link rel="stylesheet" href="{{ webpack_asset('styles/theme-' + theme() + '.css') }}">
-      {% endif %}
-    {% endif %}
-  {% endblock %}
+  {% include '../widget/headers/styles-for-app.html' %}
+  {% if 'kibela' === getConfig('crowi', 'customize:layout') %}
+    {% include '../widget/headers/styles-theme-kibela.html' %}
+  {% else %}
+    {% block theme_css_block %}
+      {% set themeName = getConfig('crowi', 'customize:theme') %}
+      {% include '../widget/headers/styles-theme.html' with {themeName: themeName} %}
+    {% endblock %}
+  {% endif %}
 
   {{ cdnStyleTagsByGroup('basis') }}
-  {{ cdnHighlightJsStyleTag(highlightJsStyle()) }}
+  {{ cdnHighlightJsStyleTag(getConfig('crowi', 'customize:highlightJsStyle')) }}
 
   {% block html_additional_headers %}{% endblock %}
 
   <style>
-    {{ customCss() }}
+    {{ customizeService.getCustomCss() }}
   </style>
 </head>
 {% endblock %}
@@ -109,10 +62,10 @@
 {% block html_body %}
 <body
   class="main-container content-wrapper {% block html_base_css %}{% endblock %}
-      {% if !layoutType() || 'crowi' === layoutType() %}crowi{% elseif !layoutType() || 'kibela' === layoutType() %}kibela{% else %}growi{% endif %}"
+      {% if !getConfig('crowi', 'customize:layout') || 'crowi' === getConfig('crowi', 'customize:layout') %}crowi{% elseif !getConfig('crowi', 'customize:layout') || 'kibela' === getConfig('crowi', 'customize:layout') %}kibela{% else %}growi{% endif %}"
   data-me="{{ user._id.toString() }}"
   data-is-admin="{{ user.admin }}"
-  data-plugin-enabled="{{ isEnabledPlugins() }}"
+  data-plugin-enabled="{{ getConfig('crowi', 'plugin:isEnabledPlugins') }}"
   {% block html_base_attr %}{% endblock %}
   data-csrftoken="{{ csrf() }}"
   data-current-username="{% if user %}{{ user.username }}{% endif %}"
@@ -133,7 +86,7 @@
             <div class="logo-mark">{% include '../widget/logo.html' %}</div>
           </b>
           <span class="hidden-xs" style="color: black">
-            {% set appTitle = appTitle() %}
+            {% set appTitle = appService.getAppTitle() %}
             {% set appTitleFontSize = getAppTitleFontSize(appTitle) %}
             <span class="logo-text">
               <svg xmlns="http://www.w3.org/2000/svg">
@@ -201,8 +154,8 @@
         {% else %}
         <li id="login-user"><a href="/login">Login</a></li>
         {% endif %}
-        {% if config.crowi['app:confidential'] && config.crowi['app:confidential'] != '' %}
-        <li class="confidential"><a href="#">{{ config.crowi['app:confidential'] }}</a></li>
+        {% if getConfig('crowi', 'app:confidential') %}
+        <li class="confidential"><a href="#">{{ getConfig('crowi', 'app:confidential') }}</a></li>
         {% endif %}
       </ul>
     </div><!-- /.navbar-header -->
@@ -255,7 +208,7 @@
 
 {% block custom_script %}
 <script>
-  {{ customScript() }}
+  {{ customizeService.getCustomScript() }}
 </script>
 {% endblock %}
 

+ 27 - 50
src/server/views/login.html

@@ -2,7 +2,7 @@
 
 {% block html_base_css %}login-page nologin{% endblock %}
 
-{% block html_title %}{{ customTitle(t('Sign in')) }}{% endblock %}
+{% block html_title %}{{ customizeService.generateCustomTitle(t('Sign in')) }}{% endblock %}
 
 
 
@@ -27,7 +27,7 @@
   <div class="row">
     <div class="login-header col-sm-offset-4 col-sm-4">
       <div class="logo">{% include 'widget/logo.html' %}</div>
-      <h1>{{ appTitle() }}</h1>
+      <h1>{{ appService.getAppTitle() }}</h1>
 
       <div class="login-form-errors">
         {% if isLdapSetupFailed() %}
@@ -100,7 +100,7 @@
       </div>
     </div>
 
-    <div class="login-dialog p-b-10 col-sm-offset-4 col-sm-4 flipper {% if req.query.register or req.body.registerForm or isRegistering or googleId %}to-flip{% endif %}" id="login-dialog">
+    <div class="login-dialog p-b-10 col-sm-offset-4 col-sm-4 flipper {% if req.query.register or req.body.registerForm or isRegistering %}to-flip{% endif %}" id="login-dialog">
 
       <div class="front">
         <form role="form" action="/login" method="post">
@@ -130,26 +130,20 @@
           </div>
         </form>
 
-        {% if googleLoginEnabled() %}
-        <hr>
-
-        <div class="input-group m-t-15 m-b-10 mx-auto">
-          <form role="form" action="/login/google" method="get">
-            <input type="hidden" name="_csrf" value="{{ csrf() }}">
-            <button type="submit" class="fcbtn btn btn-danger btn-1b btn-login-oauth" id="google">
-              <span class="btn-label"><i class="icon-social-google"></i></span>
-              {{ t('Sign in') }}
-            </button>
-            <div class="small text-right">by Google Account</div>
-          </form>
-        </div>
-        {% endif %}
-        {% if passportGoogleLoginEnabled() || passportGitHubLoginEnabled() || passportFacebookLoginEnabled() || passportTwitterLoginEnabled() || passportOidcLoginEnabled() || passportSamlLoginEnabled() || passportBasicLoginEnabled() %}
+        {% if (
+          getConfig('crowi', 'security:passport-google:isEnabled') ||
+          getConfig('crowi', 'security:passport-github:isEnabled') ||
+          getConfig('crowi', 'security:passport-facebook:isEnabled') ||
+          getConfig('crowi', 'security:passport-twitter:isEnabled')||
+          getConfig('crowi', 'security:passport-oidc:isEnabled') ||
+          getConfig('crowi', 'security:passport-saml:isEnabled') ||
+          getConfig('crowi', 'security:passport-basic:isEnabled')
+        ) %}
         <hr class="mb-1">
         <div class="collapse collapse-oauth collapse-anchor">
           <div class="spacer"></div>
           <div class="d-flex flex-row justify-content-between flex-wrap">
-            {% if passportGoogleLoginEnabled() %}
+            {% if getConfig('crowi', 'security:passport-google:isEnabled') %}
             <form role="form" action="/passport/google" class="d-inline-flex flex-column">
               <button type="submit" class="fcbtn btn btn-1b btn-login-oauth d-flex" id="google">
                 <span class="btn-label"><i class="fa fa-google"></i></span>
@@ -158,7 +152,7 @@
               <div class="small text-right">by Google Account</div>
             </form>
             {% endif %}
-            {% if passportGitHubLoginEnabled() %}
+            {% if getConfig('crowi', 'security:passport-github:isEnabled') %}
             <form role="form" action="/passport/github" class="d-inline-flex flex-column">
               <input type="hidden" name="_csrf" value="{{ csrf() }}">
               <button type="submit" class="fcbtn btn btn-1b btn-login-oauth d-inline-flex" id="github">
@@ -168,7 +162,7 @@
               <div class="small text-right">by GitHub Account</div>
             </form>
             {% endif %}
-            {% if passportFacebookLoginEnabled() %}
+            {% if getConfig('crowi', 'security:passport-facebook:isEnabled') %}
             <form role="form" action="/passport/facebook" class="d-inline-flex flex-column">
               <input type="hidden" name="_csrf" value="{{ csrf() }}">
               <button type="submit" class="fcbtn btn btn-1b btn-login-oauth d-inline-flex" id="facebook">
@@ -178,7 +172,7 @@
               <div class="small text-right">by Facebook Account</div>
             </form>
             {% endif %}
-            {% if passportTwitterLoginEnabled() %}
+            {% if getConfig('crowi', 'security:passport-twitter:isEnabled') %}
             <form role="form" action="/passport/twitter" class="d-inline-flex flex-column">
               <input type="hidden" name="_csrf" value="{{ csrf() }}">
               <button type="submit" class="fcbtn btn btn-1b btn-login-oauth d-inline-flex" id="twitter">
@@ -188,17 +182,17 @@
               <div class="small text-right">by Twitter Account</div>
             </form>
             {% endif %}
-            {% if passportOidcLoginEnabled() %}
+            {% if getConfig('crowi', 'security:passport-oidc:isEnabled') %}
             <form role="form" action="/passport/oidc" class="d-inline-flex flex-column">
               <input type="hidden" name="_csrf" value="{{ csrf() }}">
               <button type="submit" class="fcbtn btn btn-1b btn-login-oauth d-inline-flex" id="oidc">
                 <span class="btn-label"><i class="fa fa-openid"></i></span>
                 <span class="btn-label-text">{{ t('Sign in') }}</span>
               </button>
-              <div class="small text-right">{{ config.crowi['security:passport-oidc:providerName'] || "OpenID Connect" }}</div>
+              <div class="small text-right">{{ getConfig('crowi', 'security:passport-oidc:providerName') || "OpenID Connect" }}</div>
             </form>
             {% endif %}
-            {% if passportSamlLoginEnabled() %}
+            {% if getConfig('crowi', 'security:passport-saml:isEnabled') %}
             <form role="form" action="/passport/saml" class="d-inline-flex flex-column">
               <input type="hidden" name="_csrf" value="{{ csrf() }}">
               <button type="submit" class="fcbtn btn btn-1b btn-login-oauth d-inline-flex" id="saml">
@@ -208,7 +202,7 @@
               <div class="small text-right">with SAML</div>
             </form>
             {% endif %}
-            {% if passportBasicLoginEnabled() %}
+            {% if getConfig('crowi', 'security:passport-basic:isEnabled') %}
             <form role="form" action="/passport/basic" class="d-inline-flex flex-column">
               <input type="hidden" name="_csrf" value="{{ csrf() }}">
               <button type="submit" class="fcbtn btn btn-1b btn-login-oauth d-inline-flex" id="basic">
@@ -245,7 +239,7 @@
 
         <div class="row">
           <div class="col-xs-12 text-right">
-            {% if config.crowi['security:registrationMode'] != 'Closed' %}
+            {% if getConfig('crowi', 'security:registrationMode') != 'Closed' %}
             <a href="#register" id="register" class="link-switch">
               <i class="ti-check-box"></i> {{ t('Sign up is here') }}
             </a>
@@ -257,30 +251,16 @@
       </div>
 
 
-      {% if config.crowi['security:registrationMode'] != 'Closed' %}
+      {% if getConfig('crowi', 'security:registrationMode') != 'Closed' %}
       <div class="back">
-        {% if config.crowi['security:registrationMode'] == 'Restricted' %}
+        {% if getConfig('crowi', 'security:registrationMode') == 'Restricted' %}
         <p class="alert alert-warning">
           {{ t('page_register.notice.restricted') }}<br>
           {{ t('page_register.notice.restricted_defail') }}
         </p>
         {% endif %}
 
-        {% if googleId %}
-        <div class="google-info alert alert-info">
-          {% if googleImage %}
-          <p class="text-center">
-            <img src="{{ googleImage }}" class="img-circle img-circle-lg">
-          </p>
-          {% endif %}
-          <code>{{ googleEmail }}</code> {{ t('page_register with this Google Account') }}<br>
-          {{ t('page_register.notice.google_account_continue') }}
-        </div>
-        {% endif %}
-
         <form role="form" method="post" action="/register" id="register-form">
-          <input type="hidden" class="form-control" name="registerForm[googleId]" value="{{ googleId|default(req.body.registerForm.googleId) }}">
-
           <div class="input-group" id="input-group-username">
             <span class="input-group-addon"><i class="icon-user"></i></span>
             <input type="text" class="form-control" placeholder="{{ t('User ID') }}" name="registerForm[username]" value="{{ req.body.registerForm.username }}" required>
@@ -291,19 +271,19 @@
 
           <div class="input-group">
             <span class="input-group-addon"><i class="icon-tag"></i></span>
-            <input type="text" class="form-control" placeholder="{{ t('Name') }}" name="registerForm[name]" value="{{ googleName|default(req.body.registerForm.name) }}" required>
+            <input type="text" class="form-control" placeholder="{{ t('Name') }}" name="registerForm[name]" value="{{ req.body.registerForm.name }}" required>
           </div>
 
           <div class="input-group">
             <span class="input-group-addon"><i class="icon-envelope"></i></span>
-            <input type="email" class="form-control" placeholder="{{ t('Email') }}" name="registerForm[email]" value="{{ googleEmail|default(req.body.registerForm.email) }}" required>
+            <input type="email" class="form-control" placeholder="{{ t('Email') }}" name="registerForm[email]" value="{{ req.body.registerForm.email }}" required>
           </div>
-          {% if config.crowi['security:registrationWhiteList'] && config.crowi['security:registrationWhiteList'].length %}
+          {% if getConfig('crowi', 'security:registrationWhiteList') && getConfig('crowi', 'security:registrationWhiteList').length %}
           <p class="help-block">
             {{ t('page_register.form_help.email') }}
           </p>
           <ul>
-            {% for em in config.crowi['security:registrationWhiteList'] %}
+            {% for em in getConfig('crowi', 'security:registrationWhiteList') %}
             <li><code>{{ em }}</code></li>
             {% endfor %}
           </ul>
@@ -314,9 +294,6 @@
             <input type="password" class="form-control" placeholder="{{ t('Password') }}" name="registerForm[password]" required>
           </div>
 
-          {% if googleImage %}
-            <input type="hidden" name="registerForm[googleImage]" value="{{ googleImage }}">
-          {% endif  %}
           <input type="hidden" name="_csrf" value="{{ csrf() }}">
 
           <div class="input-group m-t-30 m-b-20 d-flex justify-content-center">

+ 1 - 1
src/server/views/login/error.html

@@ -2,7 +2,7 @@
 
 {% block html_base_css %}error nologin{% endblock %}
 
-{% block html_title %}{{ customTitle('セットアップ') }}{% endblock %}
+{% block html_title %}{{ customizeService.generateCustomTitle('セットアップ') }}{% endblock %}
 
 
 

+ 1 - 3
src/server/views/me/api_token.html

@@ -1,7 +1,7 @@
 {% extends '../layout-growi/base/layout.html' %}
 
 
-{% block html_title %}{{ customTitle(t('API Settings')) }}{% endblock %}
+{% block html_title %}{{ customizeService.generateCustomTitle(t('API Settings')) }}{% endblock %}
 
 
 {% block content_header %}
@@ -17,9 +17,7 @@
 
   <ul class="nav nav-tabs">
     <li><a href="/me"><i class="icon-user"></i> {{ t('User Information') }}</a></li>
-    {% if isEnabledPassport() %}
     <li><a href="/me/external-accounts"><i class="icon-share-alt"></i> {{ t('External Accounts') }}</a></li>
-    {% endif %}
     <li><a href="/me/password"><i class="icon-lock"></i> {{ t('Password Settings') }}</a></li>
     <li class="active"><a href="/me/apiToken"><i class="icon-paper-plane"></i> {{ t('API Settings') }}</a></li>
   </ul>

+ 1 - 1
src/server/views/me/external-accounts.html

@@ -1,6 +1,6 @@
 {% extends '../layout-growi/base/layout.html' %}
 
-{% block html_title %}{{ customTitle(t('user_management.external_account')) }}{% endblock %}
+{% block html_title %}{{ customizeService.generateCustomTitle(t('user_management.external_account')) }}{% endblock %}
 
 {% block content_header %}
 <div class="header-wrap">

Some files were not shown because too many files changed in this diff