Forráskód Böngészése

Merge pull request #648 from weseek/feat/GC-1097-user-privateWiki-restriction

Feat/gc 1097 user private wiki restriction
Yuki Takei 7 éve
szülő
commit
b9b4147606

+ 1 - 0
config/env.dev.js

@@ -8,6 +8,7 @@ module.exports = {
     // 'growi-plugin-lsx',
     // 'growi-plugin-pukiwiki-like-linker',
   ],
+  // USER_UPPER_LIMIT: 0,
   // DEV_HTTPS: true,
   // PUBLIC_WIKI_ONLY: true,
 };

+ 3 - 1
resource/locales/en-US/translation.json

@@ -517,7 +517,9 @@
     "Deactivate account":"Deactivate account",
     "your_own":"You cannot deactivate your own account",
     "Administrator menu":"Administrator menu",
-    "cannot_remove":"You cannot remove yourself from administrator"
+    "cannot_remove":"You cannot remove yourself from administrator",
+    "cannot_invite_maximum_users": "Can not invite more than the maximum number of users.",
+    "current users": "Current users:"
   },
 
   "importer_management": {

+ 3 - 1
resource/locales/ja/translation.json

@@ -534,7 +534,9 @@
     "Deactivate account": "アカウント停止",
     "your_own": "自分自身のアカウントを停止することはできません",
     "Administrator menu": "管理者メニュー",
-    "cannot_remove": "自分自身を管理者から外すことはできません"
+    "cannot_remove": "自分自身を管理者から外すことはできません",
+    "cannot_invite_maximum_users": "ユーザーが上限に達したため招待できません。",
+    "current users": "現在のユーザー数:"
   },
 
   "importer_management": {

+ 10 - 0
src/server/models/config.js

@@ -610,6 +610,16 @@ module.exports = function(crowi) {
     return local_config;
   };
 
+  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)
   {

+ 51 - 13
src/server/models/user.js

@@ -223,14 +223,18 @@ module.exports = function(crowi) {
     return this.updateGoogleId(null, callback);
   };
 
-  userSchema.methods.activateInvitedUser = function(username, name, password, callback) {
+  userSchema.methods.activateInvitedUser = async function(username, name, password) {
     this.setPassword(password);
     this.name = name;
     this.username = username;
     this.status = STATUS_ACTIVE;
+
     this.save(function(err, userData) {
       userEvent.emit('activated', userData);
-      return callback(err, userData);
+      if (err) {
+        throw new Error(err);
+      }
+      return userData;
     });
   };
 
@@ -422,16 +426,16 @@ module.exports = function(crowi) {
       });
   };
 
-  userSchema.statics.findUsersWithPagination = function(options, callback) {
+  userSchema.statics.findUsersWithPagination = async function(options) {
     var sort = options.sort || {status: 1, username: 1, createdAt: 1};
 
-    this.paginate({status: { $ne: STATUS_DELETED }}, { page: options.page || 1, limit: options.limit || PAGE_ITEMS }, function(err, result) {
+    return await this.paginate({status: { $ne: STATUS_DELETED }}, { page: options.page || 1, limit: options.limit || PAGE_ITEMS }, function(err, result) {
       if (err) {
         debug('Error on pagination:', err);
-        return callback(err, null);
+        throw new Error(err);
       }
 
-      return callback(err, result);
+      return result;
     }, { sortBy: sort });
   };
 
@@ -501,16 +505,38 @@ module.exports = function(crowi) {
     });
   };
 
-  userSchema.statics.isRegisterableUsername = function(username, callback) {
+  userSchema.statics.isUserCountExceedsUpperLimit = async function() {
+    const Config = crowi.model('Config');
+    const userUpperLimit = Config.userUpperLimit(crowi);
+    if (userUpperLimit === 0) {
+      return false;
+    }
+
+    const activeUsers = await this.countListByStatus(STATUS_ACTIVE);
+    if (userUpperLimit !== 0 && userUpperLimit <= activeUsers) {
+      return true;
+    }
+
+    return false;
+  };
+
+  userSchema.statics.countListByStatus = async function(status) {
+    const User = this;
+    const conditions = {status: status};
+
+    // TODO count は非推奨。mongoose のバージョンアップ後に countDocuments に変更する。
+    return User.count(conditions);
+  };
+
+  userSchema.statics.isRegisterableUsername = async function(username) {
     var User = this;
     var usernameUsable = true;
 
-    this.findOne({username: username}, function(err, userData) {
-      if (userData) {
-        usernameUsable = false;
-      }
-      return callback(usernameUsable);
-    });
+    const userData = await this.findOne({username: username});
+    if (userData) {
+      usernameUsable = false;
+    }
+    return usernameUsable;
   };
 
   userSchema.statics.isRegisterable = function(email, username, callback) {
@@ -701,6 +727,13 @@ module.exports = function(crowi) {
     const User = this
       , newUser = new User();
 
+    // check user upper limit
+    const isUserCountExceedsUpperLimit = await User.isUserCountExceedsUpperLimit();
+    if (isUserCountExceedsUpperLimit) {
+      const err = new UserUpperLimitException();
+      return callback(err);
+    }
+
     // check email duplication because email must be unique
     const count = await this.count({ email });
     if (count > 0) {
@@ -773,6 +806,11 @@ module.exports = function(crowi) {
     return username;
   };
 
+  class UserUpperLimitException {
+    constructor() {
+      this.name = this.constructor.name;
+    }
+  }
 
   userSchema.statics.STATUS_REGISTERED  = STATUS_REGISTERED;
   userSchema.statics.STATUS_ACTIVE      = STATUS_ACTIVE;

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

@@ -469,16 +469,23 @@ module.exports = function(crowi, app) {
   };
 
   actions.user = {};
-  actions.user.index = function(req, res) {
+  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 isUserCountExceedsUpperLimit = await User.isUserCountExceedsUpperLimit();
+
     var page = parseInt(req.query.page) || 1;
 
-    User.findUsersWithPagination({page: page}, function(err, result) {
-      const pager = createPager(result.total, result.limit, result.page, result.pages, MAX_PAGE_LIST);
+    const result = await User.findUsersWithPagination({page: page});
+    const pager = createPager(result.total, result.limit, result.page, result.pages, MAX_PAGE_LIST);
 
-      return res.render('admin/users', {
-        users: result.docs,
-        pager: pager
-      });
+    return res.render('admin/users', {
+      users: result.docs,
+      pager: pager,
+      activeUsers: activeUsers,
+      userUpperLimit: userUpperLimit,
+      isUserCountExceedsUpperLimit: isUserCountExceedsUpperLimit
     });
   };
 
@@ -534,7 +541,14 @@ module.exports = function(crowi, app) {
     });
   };
 
-  actions.user.activate = function(req, res) {
+  actions.user.activate = async function(req, res) {
+    // check user upper limit
+    const isUserCountExceedsUpperLimit = await User.isUserCountExceedsUpperLimit();
+    if (isUserCountExceedsUpperLimit) {
+      req.flash('errorMessage', 'ユーザーが上限に達したため有効化できません。');
+      return res.redirect('/admin/users');
+    }
+
     var id = req.params.id;
     User.findById(id, function(err, userData) {
       userData.statusActivate(function(err, userData) {

+ 4 - 0
src/server/routes/login-passport.js

@@ -415,6 +415,10 @@ module.exports = function(crowi, app) {
           return;
         }
       }
+      else if (err.name === 'UserUpperLimitException') {
+        req.flash('warningMessage', 'Can not register more than the maximum number of users.');
+        return;
+      }
     }
   };
 

+ 27 - 17
src/server/routes/login.js

@@ -188,7 +188,12 @@ module.exports = function(crowi, app) {
 
         User.createUserByEmailAndPassword(name, username, email, password, lang, function(err, userData) {
           if (err) {
-            req.flash('registerWarningMessage', 'Failed to register.');
+            if (err.name === 'UserUpperLimitException') {
+              req.flash('registerWarningMessage', 'Can not register more than the maximum number of users.');
+            }
+            else {
+              req.flash('registerWarningMessage', 'Failed to register.');
+            }
             return res.redirect('/register');
           }
           else {
@@ -328,7 +333,7 @@ module.exports = function(crowi, app) {
     });
   };
 
-  actions.invited = function(req, res) {
+  actions.invited = async function(req, res) {
     if (!req.user) {
       return res.redirect('/login');
     }
@@ -340,24 +345,29 @@ module.exports = function(crowi, app) {
       var name = invitedForm.name;
       var password = invitedForm.password;
 
-      User.isRegisterableUsername(username, function(creatable) {
-        if (creatable) {
-          user.activateInvitedUser(username, name, password, function(err, data) {
-            if (err) {
-              req.flash('warningMessage', 'アクティベートに失敗しました。');
-              return res.render('invited');
-            }
-            else {
-              return res.redirect('/');
-            }
-          });
+      // check user upper limit
+      const isUserCountExceedsUpperLimit = await User.isUserCountExceedsUpperLimit();
+      if (isUserCountExceedsUpperLimit) {
+        req.flash('warningMessage', 'ユーザーが上限に達したためアクティベートできません。');
+        return res.redirect('/invited');
+      }
+
+      const creatable = await User.isRegisterableUsername(username);
+      if (creatable) {
+        try {
+          await user.activateInvitedUser(username, name, password);
+          return res.redirect('/');
         }
-        else {
-          req.flash('warningMessage', '利用できないユーザーIDです。');
-          debug('username', username);
+        catch (err) {
+          req.flash('warningMessage', 'アクティベートに失敗しました。');
           return res.render('invited');
         }
-      });
+      }
+      else {
+        req.flash('warningMessage', '利用できないユーザーIDです。');
+        debug('username', username);
+        return res.render('invited');
+      }
     }
     else {
       return res.render('invited', {

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

@@ -33,7 +33,7 @@
 
     <div class="col-md-9">
       <p>
-        <button data-toggle="collapse" class="btn btn-default" href="#inviteUserForm">
+        <button data-toggle="collapse" class="btn btn-default" href="#inviteUserForm" {% if isUserCountExceedsUpperLimit %}disabled{% endif %}>
           {{ t("user_management.invite_users") }}
         </button>
         <a class="btn btn-default btn-outline" href="/admin/users/external-accounts">
@@ -56,6 +56,13 @@
         <input type="hidden" name="_csrf" value="{{ csrf() }}">
       </form>
 
+      {% if isUserCountExceedsUpperLimit === true %}
+      <label>{{ t('user_management.cannot_invite_maximum_users') }}</label>
+      {% endif %}
+      {% if userUpperLimit !== 0 %}
+      <label>{{ t('user_management.current users') }}{{ activeUsers }}</label>
+      {% endif %}
+
       {% set createdUser = req.flash('createdUser') %}
       {% if createdUser.length %}
       <div class="modal fade in" id="createdUserModal">