Explorar o código

Merge pull request #538 from weseek/imprv/535-forbidden-page

Imprv/535 forbidden page
Yuki Takei %!s(int64=7) %!d(string=hai) anos
pai
achega
a83b8e9823

+ 17 - 4
lib/models/page.js

@@ -1,3 +1,16 @@
+/**
+ * The Exception class thrown when the user has no grant to see the page
+ *
+ * @class UserHasNoGrantException
+ */
+class UserHasNoGrantException {
+  constructor(message, user) {
+    this.name = this.constructor.name;
+    this.message = message;
+    this.user = user;
+  }
+}
+
 module.exports = function(crowi) {
   var debug = require('debug')('growi:models:page')
     , mongoose = require('mongoose')
@@ -511,13 +524,13 @@ module.exports = function(crowi) {
         if (!pageData.isGrantedFor(userData)) {
           PageGroupRelation.isExistsGrantedGroupForPageAndUser(pageData, userData)
             .then(isExists => {
-              if (!isExists) {
-                return reject(new Error('Page is not granted for the user')); //PAGE_GRANT_ERROR, null);
-              }
-              else {
+              if (isExists) {
                 // return resolve(pageData);
                 self.populatePageData(pageData, revisionId || null).then(resolve).catch(reject);
               }
+              else {
+                return reject(new UserHasNoGrantException('Page is not granted for the user', userData));
+              }
             })
             .catch(function(err) {
               return reject(err);

+ 75 - 52
lib/routes/page.js

@@ -1,7 +1,7 @@
 module.exports = function(crowi, app) {
   'use strict';
 
-  var debug = require('debug')('growi:routes:page')
+  const debug = require('debug')('growi:routes:page')
     , logger = require('@alias/logger')('growi:routes:page')
     , Page = crowi.model('Page')
     , User = crowi.model('User')
@@ -9,7 +9,6 @@ module.exports = function(crowi, app) {
     , config   = crowi.getConfig()
     , Revision = crowi.model('Revision')
     , Bookmark = crowi.model('Bookmark')
-    , UserGroupRelation = crowi.model('UserGroupRelation')
     , PageGroupRelation = crowi.model('PageGroupRelation')
     , UpdatePost = crowi.model('UpdatePost')
     , ApiResponse = require('../util/apiResponse')
@@ -221,7 +220,7 @@ module.exports = function(crowi, app) {
   };
 
   actions.pageListShowForCrowiPlus = function(req, res) {
-    var path = getPathFromRequest(req);
+    let path = getPathFromRequest(req);
     // omit the slash of the last
     path = path.replace((/\/$/), '');
     // redirect
@@ -229,25 +228,25 @@ module.exports = function(crowi, app) {
   };
 
   actions.pageShowForCrowiPlus = function(req, res) {
-    var path = getPathFromRequest(req);
+    const path = getPathFromRequest(req);
 
-    var limit = 50;
-    var offset = parseInt(req.query.offset)  || 0;
-    var SEENER_THRESHOLD = 10;
+    const limit = 50;
+    const offset = parseInt(req.query.offset)  || 0;
+    const SEENER_THRESHOLD = 10;
 
     // index page
-    var pagerOptions = {
+    const pagerOptions = {
       offset: offset,
       limit: limit
     };
-    var queryOptions = {
+    const queryOptions = {
       offset: offset,
       limit: limit + 1,
       isPopulateRevisionBody: Config.isEnabledTimeline(config),
       includeDeletedPage: path.startsWith('/trash/'),
     };
 
-    var renderVars = {
+    const renderVars = {
       path: path,
       page: null,
       revision: {},
@@ -259,9 +258,9 @@ module.exports = function(crowi, app) {
       slack: '',
     };
 
-    var pageTeamplate = 'customlayout-selector/page';
+    let view = 'customlayout-selector/page';
 
-    var isRedirect = false;
+    let isRedirect = false;
     Page.findPage(path, req.user, req.query.revision)
     .then(function(page) {
       debug('Page found', page._id, page.path);
@@ -299,12 +298,12 @@ module.exports = function(crowi, app) {
           renderVars.slack = channels;
         })
         .then(function() {
-          var userPage = isUserPage(page.path);
-          var userData = null;
+          const userPage = isUserPage(page.path);
+          let userData = null;
 
           if (userPage) {
             // change template
-            pageTeamplate = 'customlayout-selector/user_page';
+            view = 'customlayout-selector/user_page';
 
             return User.findUserByUsername(User.getUsernameByPath(page.path))
             .then(function(data) {
@@ -330,18 +329,30 @@ module.exports = function(crowi, app) {
         });
       }
     })
-    // look for templates if page not exists
+    // page is not found or user is forbidden
     .catch(function(err) {
-      pageTeamplate = 'customlayout-selector/not_found';
+      let isForbidden = false;
+      if (err.name === 'UserHasNoGrantException') {
+        isForbidden = true;
+      }
 
-      return Page.findTemplate(path)
-        .then(template => {
-          if (template) {
-            template = replacePlaceholders(template, req);
-          }
+      if (isForbidden) {
+        view = 'customlayout-selector/forbidden';
+        return;
+      }
+      else {
+        view = 'customlayout-selector/not_found';
 
-          renderVars.template = template;
-        });
+        // look for templates
+        return Page.findTemplate(path)
+          .then(template => {
+            if (template) {
+              template = replacePlaceholders(template, req);
+            }
+
+            renderVars.template = template;
+          });
+      }
     })
     // get list pages
     .then(function() {
@@ -366,11 +377,10 @@ module.exports = function(crowi, app) {
             return interceptorManager.process('beforeRenderPage', req, res, renderVars);
           })
           .then(function() {
-            res.render(req.query.presentation ? 'page_presentation' : pageTeamplate, renderVars);
+            res.render(req.query.presentation ? 'page_presentation' : view, renderVars);
           })
           .catch(function(err) {
-            console.log(err);
-            debug('Error on rendering pageListShowForCrowiPlus', err);
+            logger.error('Error on rendering pageListShowForCrowiPlus', err);
           });
       }
     });
@@ -467,22 +477,28 @@ module.exports = function(crowi, app) {
     });
   };
 
-  function renderPage(pageData, req, res) {
-    // create page
+  async function renderPage(pageData, req, res, isForbidden) {
     if (!pageData) {
-      const path = getPathFromRequest(req);
-      return Page.findTemplate(path)
-        .then(template => {
-          if (template) {
-            template = replacePlaceholders(template, req);
-          }
+      let view = 'customlayout-selector/not_found';
+      let template = undefined;
 
-          return res.render('customlayout-selector/not_found', {
-            author: {},
-            page: false,
-            template,
-          });
-        });
+      // forbidden
+      if (isForbidden) {
+        view = 'customlayout-selector/forbidden';
+      }
+      else {
+        const path = getPathFromRequest(req);
+        template = await Page.findTemplate(path);
+        if (template != null) {
+          template = replacePlaceholders(template, req);
+        }
+      }
+
+      return res.render(view, {
+        author: {},
+        page: false,
+        template,
+      });
     }
 
 
@@ -490,15 +506,15 @@ module.exports = function(crowi, app) {
       return res.redirect(encodeURI(pageData.redirectTo + '?redirectFrom=' + pagePathUtil.encodePagePath(pageData.path)));
     }
 
-    var renderVars = {
+    const renderVars = {
       path: pageData.path,
       page: pageData,
       revision: pageData.revision || {},
       author: pageData.revision.author || false,
       slack: '',
     };
-    var userPage = isUserPage(pageData.path);
-    var userData = null;
+    const userPage = isUserPage(pageData.path);
+    let userData = null;
 
     Revision.findRevisionList(pageData.path, {})
     .then(function(tree) {
@@ -547,11 +563,11 @@ module.exports = function(crowi, app) {
     }).then(function() {
       return interceptorManager.process('beforeRenderPage', req, res, renderVars);
     }).then(function() {
-      var defaultPageTeamplate = 'customlayout-selector/page';
+      let view = 'customlayout-selector/page';
       if (userData) {
-        defaultPageTeamplate = 'customlayout-selector/user_page';
+        view = 'customlayout-selector/user_page';
       }
-      res.render(req.query.presentation ? 'page_presentation' : defaultPageTeamplate, renderVars);
+      res.render(req.query.presentation ? 'page_presentation' : view, renderVars);
     }).catch(function(err) {
       debug('Error: renderPage()', err);
       if (err) {
@@ -578,7 +594,14 @@ module.exports = function(crowi, app) {
       }
 
       return renderPage(page, req, res);
-    }).catch(function(err) {
+    })
+    // page is not found or the user is forbidden
+    .catch(function(err) {
+
+      let isForbidden = false;
+      if (err.name === 'UserHasNoGrantException') {
+        isForbidden = true;
+      }
 
       const normalizedPath = Page.normalizePath(path);
       if (normalizedPath !== path) {
@@ -589,7 +612,7 @@ module.exports = function(crowi, app) {
       // これ以前に定義されているはずなので、こうしてしまって問題ない。
       if (!Page.isCreatableName(path)) {
         // 削除済みページの場合 /trash 以下に移動しているので creatableName になっていないので、表示を許可
-        debug('Page is not creatable name.', path);
+        logger.warn('Page is not creatable name.', path);
         res.redirect('/');
         return ;
       }
@@ -607,9 +630,9 @@ module.exports = function(crowi, app) {
           return res.redirect(pagePathUtil.encodePagePath(path) + '/');
         }
         else {
-          var fixed = Page.fixToCreatableName(path);
+          const fixed = Page.fixToCreatableName(path);
           if (fixed !== path) {
-            debug('fixed page name', fixed);
+            logger.warn('fixed page name', fixed);
             res.redirect(pagePathUtil.encodePagePath(fixed));
             return ;
           }
@@ -621,7 +644,7 @@ module.exports = function(crowi, app) {
 
           // render editor
           debug('Catch pageShow', err);
-          return renderPage(null, req, res);
+          return renderPage(null, req, res, isForbidden);
         }
       }).catch(function(err) {
         debug('Error on rendering pageShow (redirect to portal)', err);

+ 5 - 0
lib/views/customlayout-selector/forbidden.html

@@ -0,0 +1,5 @@
+{% if !layoutType() || 'crowi' === layoutType() %}
+  {% include '../layout-crowi/forbidden.html' %}
+{% else %}
+  {% include '../layout-growi/forbidden.html' %}
+{% endif %}

+ 41 - 0
lib/views/layout-crowi/forbidden.html

@@ -0,0 +1,41 @@
+{% extends 'base/layout.html' %}
+
+{% block content_header %}
+
+  {% block content_header_before %}
+  {% endblock %}
+
+  <div class="header-wrap">
+    <header id="page-header">
+      <div>
+        <div>
+          <h1 class="title" id="revision-path"></h1>
+          <div id="revision-url" class="url-line"></div>
+        </div>
+      </div>
+
+    </header>
+  </div>
+
+  {% block content_header_after %}
+  {% endblock %}
+
+{% endblock %} {# /content_head #}
+
+
+{% block content_main_before %}
+  {% include '../widget/page_alerts.html' %}
+{% endblock %}
+
+
+{% block content_main %}
+  {% include '../widget/forbidden_content.html' %}
+{% endblock %}
+
+
+{% block content_main_after %}
+{% endblock %}
+
+
+{% block content_footer %}
+{% endblock %}

+ 25 - 0
lib/views/layout-growi/forbidden.html

@@ -0,0 +1,25 @@
+{% extends 'base/layout.html' %}
+
+
+{% block content_header %}
+  {% include 'widget/header.html' %}
+{% endblock %}
+
+
+{% block content_main_before %}
+  {% include '../widget/page_alerts.html' %}
+{% endblock %}
+
+
+{% block content_main %}
+  <div class="row">
+    <div class="col-lg-10 col-md-9">
+      {% include '../widget/forbidden_content.html' %}
+    </div> {# /.col- #}
+  </div>
+{% endblock %}
+
+{% block body_end %}
+  <div id="crowi-modals">
+  </div>
+{% endblock %}

+ 50 - 0
lib/views/widget/forbidden_content.html

@@ -0,0 +1,50 @@
+{% block html_head_loading_legacy %}
+  <script src="{{ webpack_asset('js/legacy-form.js') }}" defer></script>  {# load legacy-form for using bootstrap-select(.selectpicker) #}
+  {% parent %}
+{% endblock %}
+
+<div class="row not-found-message-row m-b-20">
+  <div class="col-md-12">
+    <h2 class="text-muted">
+      <i class="icon-ban" aria-hidden="true"></i>
+      Forbidden
+    </h2>
+  </div>
+</div>
+
+<div id="content-main" class="content-main content-main-not-found page-list"
+  data-path="{{ path | preventXss }}"
+  data-path-shortname="{{ path|path2name | preventXss }}"
+  data-current-user="{% if user %}{{ user._id.toString() }}{% endif %}"
+  >
+
+  <div class="row row-alerts">
+    <div class="col-xs-12">
+        <p class="alert alert-inverse alert-grant">
+          <i class="icon-fw icon-lock" aria-hidden="true"></i> Browsing of this page is restricted
+        </p>
+    </div>
+  </div>
+
+  <ul class="nav nav-tabs hidden-print">
+    <li class="nav-main-left-tab active">
+      <a href="#revision-body" data-toggle="tab">
+        <i class="icon-notebook"></i> List
+      </a>
+    </li>
+  </ul>
+
+  <div class="tab-content">
+    {# list view #}
+    <div class="p-t-10 active tab-pane page-list-container" id="revision-body">
+      {% if pages.length == 0 %}
+        <div class="m-t-10">
+          There are no pages under <strong>{{ path }}</strong>.
+        </div>
+      {% endif  %}
+
+      {% include '../widget/page_list.html' with { pages: pages, pager: pager, viewConfig: viewConfig } %}
+    </div>
+
+  </div>
+</div>