Просмотр исходного кода

Merge pull request #74 from weseek/feat/browsable-without-account

Feat/browsable without account
Yuki Takei 8 лет назад
Родитель
Сommit
33e600cdc3

+ 1 - 0
lib/crowi/express-init.js

@@ -71,6 +71,7 @@ module.exports = function(crowi, app) {
         pageGrants: Page.getGrantLabels(),
         userStatus: User.getUserStatusLabels(),
         language:   User.getLanguageLabels(),
+        restrictGuestMode: Config.getRestrictGuestModeLabels(),
         registrationMode: Config.getRegistrationModeLabels(),
     };
     res.locals.local_config = Config.getLocalconfig(config); // config for browser context

+ 1 - 0
lib/form/admin/sec.js

@@ -9,6 +9,7 @@ var form = require('express-form')
 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)
 );

+ 24 - 0
lib/models/config.js

@@ -6,6 +6,9 @@ module.exports = function(crowi) {
     , configSchema
     , Config
 
+    , SECURITY_RESTRICT_GUEST_MODE_DENY = 'Deny'
+    , SECURITY_RESTRICT_GUEST_MODE_READONLY = 'Readonly'
+
     , SECURITY_REGISTRATION_MODE_OPEN = 'Open'
     , SECURITY_REGISTRATION_MODE_RESTRICTED = 'Resricted'
     , SECURITY_REGISTRATION_MODE_CLOSED = 'Closed'
@@ -26,6 +29,8 @@ module.exports = function(crowi) {
 
       'app:fileUpload'    : false,
 
+      'security:restrictGuestMode'      : 'Deny',
+
       'security:registrationMode'      : 'Open',
       'security:registrationWhiteList' : [],
 
@@ -57,6 +62,15 @@ module.exports = function(crowi) {
     }
   }
 
+  configSchema.statics.getRestrictGuestModeLabels = function()
+  {
+    var labels = {};
+    labels[SECURITY_RESTRICT_GUEST_MODE_DENY]     = 'アカウントを持たないユーザーはアクセス不可';
+    labels[SECURITY_RESTRICT_GUEST_MODE_READONLY] = '閲覧のみ許可';
+
+    return labels;
+  };
+
   configSchema.statics.getRegistrationModeLabels = function()
   {
     var labels = {};
@@ -204,6 +218,16 @@ module.exports = function(crowi) {
     return method != 'none';
   };
 
+  configSchema.statics.isGuesstAllowedToRead = function(config)
+  {
+    // 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.isEnabledPlugins = function(config)
   {
     var defaultValue = getArrayForInstalling()['plugin:isEnabledPlugins'];

+ 25 - 24
lib/routes/index.js

@@ -20,7 +20,7 @@ module.exports = function(crowi, app) {
     , csrf      = middleware.csrfVerify(crowi, app)
     ;
 
-  app.get('/'                        , loginRequired(crowi, app) , page.pageListShow);
+  app.get('/'                        , middleware.applicationInstalled(), loginRequired(crowi, app, false) , page.pageListShow);
 
   app.get('/installer'               , middleware.applicationNotInstalled() , installer.index);
   app.post('/installer/createAdmin'  , middleware.applicationNotInstalled() , form.register , csrf, installer.createAdmin);
@@ -90,54 +90,55 @@ module.exports = function(crowi, app) {
   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) , page.api.redirector);
-  app.get( '/_r/:id([0-9a-z]{24})'    , loginRequired(crowi, app) , page.api.redirector); // alias
-  app.get( '/files/:id([0-9a-z]{24})' , loginRequired(crowi, app) , attachment.api.redirector);
+  app.get( '/:id([0-9a-z]{24})'       , loginRequired(crowi, app, false) , page.api.redirector);
+  app.get( '/_r/:id([0-9a-z]{24})'    , loginRequired(crowi, app, false) , page.api.redirector); // alias
+  app.get( '/files/:id([0-9a-z]{24})' , loginRequired(crowi, app, false) , attachment.api.redirector);
 
-  app.get( '/_search'                 , loginRequired(crowi, app) , search.searchPage);
-  app.get( '/_api/search'             , accessTokenParser , loginRequired(crowi, app) , search.api.search);
+  app.get( '/_search'                 , loginRequired(crowi, app, false) , search.searchPage);
+  app.get( '/_api/search'             , accessTokenParser , loginRequired(crowi, app, false) , search.api.search);
 
   app.get( '/_api/check_username'     , user.api.checkUsername);
   app.post('/_api/me/picture/upload'  , loginRequired(crowi, app) , uploads.single('userPicture'), me.api.uploadPicture);
-  app.get( '/_api/user/bookmarks'     , loginRequired(crowi, app) , user.api.bookmarks);
+  app.get( '/_api/user/bookmarks'     , loginRequired(crowi, app, false) , user.api.bookmarks);
 
-  app.get( '/user/:username([^/]+)/bookmarks'      , loginRequired(crowi, app) , page.userBookmarkList);
-  app.get( '/user/:username([^/]+)/recent-create'  , loginRequired(crowi, app) , page.userRecentCreatedList);
+  app.get( '/user/:username([^/]+)/bookmarks'      , loginRequired(crowi, app, false) , page.userBookmarkList);
+  app.get( '/user/:username([^/]+)/recent-create'  , loginRequired(crowi, app, false) , page.userRecentCreatedList);
 
   // HTTP RPC Styled API (に徐々に移行していいこうと思う)
-  app.get('/_api/users.list'          , accessTokenParser , loginRequired(crowi, app) , user.api.list);
-  app.get('/_api/pages.list'          , accessTokenParser , loginRequired(crowi, app) , page.api.list);
+  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.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) , page.api.get);
-  app.get('/_api/pages.updatePost'    , accessTokenParser , loginRequired(crowi, app) , page.api.getUpdatePost);
-  app.post('/_api/pages.seen'         , accessTokenParser , loginRequired(crowi, app) , page.api.seen);
+  app.get('/_api/pages.get'           , accessTokenParser , loginRequired(crowi, app, false) , page.api.get);
+  app.get('/_api/pages.updatePost'    , accessTokenParser , loginRequired(crowi, app, false) , page.api.getUpdatePost);
+  // 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.get('/_api/comments.get'        , accessTokenParser , loginRequired(crowi, app) , comment.api.get);
+  app.get('/_api/comments.get'        , accessTokenParser , loginRequired(crowi, app, false) , comment.api.get);
   app.post('/_api/comments.add'       , form.comment, accessTokenParser , loginRequired(crowi, app) , csrf, comment.api.add);
-  app.get( '/_api/bookmarks.get'      , accessTokenParser , loginRequired(crowi, app) , bookmark.api.get);
+  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) , attachment.api.list);
+  app.get( '/_api/attachments.list'   , accessTokenParser , loginRequired(crowi, app, false) , attachment.api.list);
   app.post('/_api/attachments.add'    , uploads.single('file'), accessTokenParser, loginRequired(crowi, app) ,csrf, attachment.api.add);
   app.post('/_api/attachments.remove' , accessTokenParser , loginRequired(crowi, app) , csrf, attachment.api.remove);
 
-  app.get( '/_api/revisions.get'      , accessTokenParser , loginRequired(crowi, app) , revision.api.get);
-  app.get( '/_api/revisions.ids'      , accessTokenParser , loginRequired(crowi, app) , revision.api.ids);
-  app.get( '/_api/revisions.list'     , accessTokenParser , loginRequired(crowi, app) , revision.api.list);
+  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('/_api/revision/:id'     , user.useUserData()         , revision.api.get);
   //app.get('/_api/r/:revisionId'    , user.useUserData()         , page.api.get);
 
   app.post('/_/edit'                 , form.revision             , loginRequired(crowi, app) , csrf, page.pageEdit);
-  app.get('/trash/$'                 , loginRequired(crowi, app) , page.deletedPageListShow);
-  app.get('/trash/*/$'               , loginRequired(crowi, app) , page.deletedPageListShow);
-  app.get('/*/$'                     , loginRequired(crowi, app) , page.pageListShow);
-  app.get('/*'                       , loginRequired(crowi, app) , page.pageShow);
+  app.get('/trash/$'                 , loginRequired(crowi, app, false) , page.deletedPageListShow);
+  app.get('/trash/*/$'               , loginRequired(crowi, app, false) , page.deletedPageListShow);
+  app.get('/*/$'                     , loginRequired(crowi, app, false) , page.pageListShow);
+  app.get('/*'                       , loginRequired(crowi, app, false) , page.pageShow);
 
 };

+ 6 - 0
lib/routes/page.js

@@ -311,6 +311,12 @@ module.exports = function(crowi, app) {
             return ;
           }
 
+          // if guest user
+          if (!req.user) {
+            res.redirect('/');
+          }
+
+          // render editor
           debug('Catch pageShow', err);
           return renderPage(null, req, res);
         }

+ 19 - 1
lib/util/middlewares.js

@@ -175,10 +175,28 @@ exports.adminRequired = function() {
   };
 };
 
-exports.loginRequired = function(crowi, app) {
+/**
+ * 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) {
     var User = crowi.model('User')
 
+    // when the route is not strictly restricted
+    if (!isStrictly) {
+      var config = req.config;
+      var Config = crowi.model('Config');
+
+      // when allowed to read
+      if (Config.isGuesstAllowedToRead(config)) {
+        return next();
+      }
+    }
+
     if (req.user && '_id' in req.user) {
       if (req.user.status === User.STATUS_ACTIVE) {
         // Active の人だけ先に進める

+ 11 - 0
lib/views/admin/app.html

@@ -103,6 +103,17 @@
           </div>
         </div>
 
+        <div class="form-group">
+          <label for="settingForm[security:restrictGuestMode]" class="col-xs-3 control-label">ゲストユーザーのアクセス</label>
+          <div class="col-xs-6">
+            <select class="form-control" name="settingForm[security:restrictGuestMode]" value="{{ settingForm['security:restrictGuestMode'] }}">
+              {% for modeValue, modeLabel in consts.restrictGuestMode %}
+              <option value="{{ modeValue }}" {% if modeValue == settingForm['security:restrictGuestMode'] %}selected{% endif %} >{{ modeLabel }}</option>
+              {% endfor %}
+            </select>
+          </div>
+        </div>
+
         <div class="form-group">
           <label for="settingForm[security:registrationMode]" class="col-xs-3 control-label">登録の制限</label>
           <div class="col-xs-6">

+ 4 - 3
lib/views/crowi-plus/widget/comments.html

@@ -24,7 +24,8 @@
         </div>
         <div class="comment-form-main">
           <div class="comment-write" id="comment-write">
-            <textarea class="comment-form-comment form-control" id="comment-form-comment" name="commentForm[comment]" rows="10" placeholder="コメントを入力してください。"></textarea>
+            <textarea class="comment-form-comment form-control" id="comment-form-comment" name="commentForm[comment]"
+                rows="10" placeholder="Write comments here..." {% if not user %}disabled{% endif %}></textarea>
           </div>
           <div class="comment-submit">
             <input type="hidden" name="_csrf" value="{{ csrf() }}">
@@ -32,8 +33,8 @@
             <input type="hidden" name="commentForm[revision_id]" value="{{ revision._id.toString() }}">
             <div class="pull-right">
               <span class="text-danger" id="comment-form-message"></span>
-              <button type="submit" id="comment-form-button" class="btn btn-primary form-inline">
-                <i class="fa fa-comment comment-ico" aria-hidden="true"></i>Comment
+              <button type="submit" id="comment-form-button" class="btn btn-primary form-inline" {% if not user %}disabled{% endif %}>
+                Comment
               </button>
             </div>
             <div class="clearfix"></div>

+ 4 - 17
lib/views/crowi-plus/widget/header.html

@@ -1,5 +1,4 @@
 <div class="header-wrap">
-  {% if not page.isDeleted() %}
   <header id="page-header">
     <p class="stopper"><a href="#" data-affix-disable="#page-header"><i class="fa fa-chevron-up"></i></a></p>
 
@@ -10,17 +9,20 @@
       </div>
       {% if page %}
       <div class="flex-item-action">
+        {% if user %}
         <span id="bookmark-button">
           <p class="bookmark-link">
             <i class="fa fa-star-o"></i>
           </p>
         </span>
+        {% endif %}
       </div>
       <div class="flex-item-action visible-xs visible-sm">
         <button
             data-csrftoken="{{ csrf() }}"
             data-liked="{% if page.isLiked(user) %}1{% else %}0{% endif %}"
             class="like-button btn btn-default btn-sm {% if page.isLiked(user) %}active{% endif %}"
+            {% if not user %}disabled{% endif %}
         ><i class="fa fa-thumbs-o-up"></i></button>
       </div>
 
@@ -52,7 +54,7 @@
 
       {% if not page and not isUserPageList(path) and !isTrashPage() %}
       <div class="portal-form-button">
-        <button class="btn btn-primary" id="create-portal-button">Create Portal</button>
+        <button class="btn btn-primary" id="create-portal-button" {% if not user %}disabled{% endif %}>Create Portal</button>
         <p class="help-block"><a href="#" data-target="#help-portal" data-toggle="modal"><i class="fa fa-question-circle"></i> What is Portal?</a></p>
       </div>
       {% endif %}
@@ -60,19 +62,4 @@
     </div>
 
   </header>
-  {% else %}
-  {# trash/* #}
-  <header id="page-header">
-    <div class="flex-title-line">
-      <h1 class="title flex-item-title"></h1>
-      <div class="flex-item-action">
-        <span id="bookmark-button">
-          <p class="bookmark-link">
-            <i class="fa fa-star-o"></i>
-          </a>
-        </span>
-      </div>
-    </div>
-  </header>
-  {% endif %}
 </div>

+ 1 - 1
lib/views/layout/layout.html

@@ -134,7 +134,7 @@
         </ul>
       </li>
       {% else %}
-      <li id="login-user"><a href="/login" id="login"><i class="fa fa-user"></i> Login</a></li>
+      <li id="login-user"><a href="/login"><i class="fa fa-user"></i> Login</a></li>
       {% endif %}
       {% if config.crowi['app:confidential'] && config.crowi['app:confidential'] != '' %}
       <li class="confidential"><a href="#">{{ config.crowi['app:confidential'] }}</a></li>

+ 14 - 19
lib/views/page.html

@@ -8,26 +8,27 @@
 {% endblock %}
 
 <div class="header-wrap">
-  {% if not page.isDeleted() %}
   <header id="page-header">
     <p class="stopper"><a href="#" data-affix-disable="#page-header"><i class="fa fa-chevron-up"></i></a></p>
 
-
     <div class="flex-title-line">
       <h1 class="title flex-item-title" id="revision-path"></h1>
       {% if page %}
       <div class="flex-item-action">
+        {% if user %}
         <span id="bookmark-button">
           <p class="bookmark-link">
             <i class="fa fa-star-o"></i>
           </p>
         </span>
+        {% endif %}
       </div>
       <div class="flex-item-action visible-xs visible-sm">
         <button
             data-csrftoken="{{ csrf() }}"
             data-liked="{% if page.isLiked(user) %}1{% else %}0{% endif %}"
             class="like-button btn btn-default btn-sm {% if page.isLiked(user) %}active{% endif %}"
+            {% if not user %}disabled{% endif %}
         ><i class="fa fa-thumbs-o-up"></i></button>
       </div>
       {% endif %}
@@ -35,21 +36,6 @@
 
     <div id="revision-url" class="url-line"></div>
   </header>
-  {% else %}
-  {# trash/* #}
-  <header id="page-header">
-    <div class="flex-title-line">
-      <h1 class="title flex-item-title"></h1>
-      <div class="flex-item-action">
-        <span id="bookmark-button">
-          <p class="bookmark-link">
-            <i class="fa fa-star-o"></i>
-          </a>
-        </span>
-      </div>
-    </div>
-  </header>
-  {% endif %}
 </div>
 
 {% block content_head_after %}
@@ -93,6 +79,7 @@
   {% if page.isDeleted() %}
   <div class="alert alert-danger alert-trash">
     <div>
+      {% if user %}
       <ul class="list-inline pull-right">
         <li>
           <form role="form" id="revert-delete-page-form" onsubmit="return false;">
@@ -118,6 +105,8 @@
           </form>
         </li>
       </ul>{# /.pull-right #}
+      {% endif %}
+
       <i class="fa fa-trash-o" aria-hidden="true"></i>
       This page is in the trash.<br>
       Deleted by <img src="{{ page.lastUpdateUser|picture }}" class="picture picture-sm picture-rounded"> {{ page.lastUpdateUser.name }} at {{ page.updatedAt|datetz('Y-m-d H:i:s') }}
@@ -136,11 +125,15 @@
       </a>
     </li>
 
-    <li {% if req.body.pageForm %}class="active"{% endif %}><a href="#edit-form" data-toggle="tab"><i class="fa fa-pencil-square-o"></i> {{ t('Edit') }}</a></li>
+    <li {% if req.body.pageForm %}class="active"{% endif %}>
+      <a {% if user %}href="#edit-form" data-toggle="tab"{% endif %} class="edit-button {% if not user %}edit-button-disabled{% endif %}">
+        <i class="fa fa-pencil-square-o"></i> {{ t('Edit') }}
+      </a>
+    </li>
 
 
     <li class="dropdown pull-right">
-      <a class="dropdown-toggle" data-toggle="dropdown" href="#">
+      <a class="dropdown-toggle {% if not user %}dropdown-disabled{% endif %}" {% if user %}data-toggle="dropdown" href="#"{% endif %}>
         <i class="fa fa-wrench"></i> <span class="caret"></span>
       </a>
       <ul class="dropdown-menu">
@@ -169,6 +162,7 @@
   {% if req.query.redirectFrom and not page.isDeleted() %}
   <div class="alert alert-info alert-moved">
     <div>
+      {% if user %}
       <form role="form" id="unlink-page-form" onsubmit="return false;">
         <input type="hidden" name="_csrf" value="{{ csrf() }}">
         <input type="hidden" name="path" value="{{ page.path }}">
@@ -178,6 +172,7 @@
           Unlink
         </button>
       </form>
+      {% endif %}
       <span>
         <strong>{{ t('Moved') }}: </strong> {{ t('page_page.notice.moved', req.query.redirectFrom) }}
       </span>

+ 9 - 2
lib/views/page_list.html

@@ -33,13 +33,16 @@
 
       {% if page %}
       <div class="flex-item-action">
+        {% if user %}
         <span id="bookmark-button" data-csrftoken="{{ csrf() }}"></span>
+        {% endif %}
       </div>
       <div class="flex-item-action visible-xs visible-sm">
         <button
             data-csrftoken="{{ csrf() }}"
             data-liked="{% if page.isLiked(user) %}1{% else %}0{% endif %}"
             class="like-button btn btn-default btn-sm {% if page.isLiked(user) %}active{% endif %}"
+            {% if not user %}disabled{% endif %}
         ><i class="fa fa-thumbs-o-up"></i></button>
       </div>
       {% endif %}
@@ -95,7 +98,11 @@
       <a>Create Portal: {{ path }}</a>
       {% endif %}
     </li>
-    <li {% if req.body.pageForm %}class="active"{% endif %}><a href="#edit-form" data-toggle="tab"><i class="fa fa-pencil-square-o"></i> {{ t('Edit') }}</a></li>
+    <li {% if req.body.pageForm %}class="active"{% endif %}>
+      <a {% if user %}href="#edit-form" data-toggle="tab"{% endif %} class="edit-button {% if not user %}edit-button-disabled{% endif %}">
+        <i class="fa fa-pencil-square-o"></i> {{ t('Edit') }}
+      </a>
+    </li>
 
     {% if not page %}
     <li class="pull-right close-button">
@@ -105,7 +112,7 @@
     </li>
     {% else %}
     <li class="dropdown pull-right">
-      <a class="dropdown-toggle" data-toggle="dropdown" href="#">
+      <a class="dropdown-toggle {% if not user %}dropdown-disabled{% endif %}" {% if user %}data-toggle="dropdown" href="#"{% endif %}>
         <i class="fa fa-wrench"></i> <span class="caret"></span>
       </a>
       <ul class="dropdown-menu">

+ 2 - 2
lib/views/widget/page_side_content.html

@@ -16,14 +16,14 @@
     <div class="comment-form">
       <div class="comment-form-main">
         <div class="comment-write" id="comment-write">
-          <textarea class="comment-form-comment form-control" id="comment-form-comment" name="commentForm[comment]"></textarea>
+          <textarea class="comment-form-comment form-control" id="comment-form-comment" name="commentForm[comment]" {% if not user %}disabled{% endif %}></textarea>
         </div>
         <div class="comment-submit">
           <input type="hidden" name="_csrf" value="{{ csrf() }}">
           <input type="hidden" name="commentForm[page_id]" value="{{ page._id.toString() }}">
           <input type="hidden" name="commentForm[revision_id]" value="{{ revision._id.toString() }}">
           <span class="text-danger" id="comment-form-message"></span>
-          <input type="submit" id="comment-form-button" value="Comment" class="btn btn-primary btn-sm form-inline">
+          <input type="submit" id="comment-form-button" value="Comment" class="btn btn-primary btn-sm form-inline" {% if not user %}disabled{% endif %}>
         </div>
       </div>
     </div>

+ 1 - 0
lib/views/widget/page_side_header.html

@@ -36,6 +36,7 @@
           data-csrftoken="{{ csrf() }}"
           data-liked="{% if page.isLiked(user) %}1{% else %}0{% endif %}"
           class="like-button btn btn-default btn-sm {% if page.isLiked(user) %}active{% endif %}"
+          {% if not user %}disabled{% endif %}
           ><i class="fa fa-thumbs-o-up"></i> {{ t('Like!') }}</button>
         </p>
         <p id="liker-list" class="liker-list" data-likers="{{ page.liker|default([])|join(',') }}">

+ 7 - 0
resource/css/crowi.scss

@@ -237,6 +237,13 @@ footer {
     }
   }
 }
+.dropdown-disabled {
+  cursor: not-allowed;
+}
+
+.edit-button.edit-button-disabled {
+  cursor: not-allowed;
+}
 
 // user picture
 .picture {

+ 11 - 0
resource/js/components/BookmarkButton.js

@@ -16,6 +16,12 @@ export default class BookmarkButton extends React.Component {
   }
 
   componentDidMount() {
+    // if guest user
+    if (this.props.crowi.me === "") {
+      // do nothing
+      return;
+    }
+
     this.props.crowi.apiGet('/bookmarks.get', {page_id: this.props.pageId})
     .then(res => {
       if (res.bookmark) {
@@ -51,6 +57,11 @@ export default class BookmarkButton extends React.Component {
   }
 
   render() {
+    // if guest user
+    if (this.props.crowi.me === "") {
+      return <div></div>;
+    }
+
     const iconName = this.state.bookmarked ? 'star' : 'star-o';
 
     return (