Browse Source

Fixes #3988 - KB custom domain url: cannot open internal articles from agents perspective

Mantas Masalskis 1 year ago
parent
commit
7858081f6b

+ 4 - 1
app/assets/javascripts/app/controllers/knowledge_base/navigation.coffee

@@ -134,7 +134,10 @@ class App.KnowledgeBaseNavigation extends App.Controller
     if !(object?.visiblePublicly?(kb_locale) or (object?.translation?(kb_locale?.id)? and @parentController.isEditor()))
       return
 
-    object.publicBaseUrl(kb_locale)
+    objectName = object.constructor.className
+    locale     = kb_locale.systemLocale().locale
+
+    "#{@apiPath}/knowledge_bases/preview/#{objectName}/#{object.id}/#{locale}"
 
   kb_locales: ->
     path = '#' + @parentController.lastParams.match.input

+ 14 - 0
app/controllers/knowledge_base/public/base_controller.rb

@@ -1,6 +1,7 @@
 # Copyright (C) 2012-2023 Zammad Foundation, https://zammad-foundation.org/
 
 class KnowledgeBase::Public::BaseController < ApplicationController
+  before_action :authenticate_with_preview_token
   before_action :load_kb
   helper_method :system_locale_via_uri, :fallback_locale, :current_user, :find_category,
                 :filter_primary_kb_locale, :menu_items, :all_locales, :can_preview?
@@ -119,4 +120,17 @@ class KnowledgeBase::Public::BaseController < ApplicationController
     respond_to_exception(publicly_visible_error, :not_found)
     http_log
   end
+
+  def authenticate_with_preview_token
+    if params[:preview_token].present?
+      session[:kb_preview_token] = params[:preview_token]
+    end
+
+    user = Token.check action: 'KnowledgeBasePreview', token: session[:kb_preview_token]
+
+    return if user.blank?
+
+    @_auth_type    = 'kb_preview_token'
+    @_current_user = user
+  end
 end

+ 18 - 0
app/controllers/knowledge_bases_controller.rb

@@ -1,6 +1,8 @@
 # Copyright (C) 2012-2023 Zammad Foundation, https://zammad-foundation.org/
 
 class KnowledgeBasesController < KnowledgeBase::BaseController
+  include KnowledgeBaseHelper
+
   def init
     render json: assets(params[:answer_translation_content_ids])
   end
@@ -9,6 +11,22 @@ class KnowledgeBasesController < KnowledgeBase::BaseController
     render json: calculate_visible_ids
   end
 
+  def preview
+    token = Token.renew_token! 'KnowledgeBasePreview'
+
+    path = case params[:object]
+           when 'KnowledgeBase'
+             help_root_path params[:locale], preview_token: token
+           when 'KnowledgeBaseCategory'
+             help_category_path params[:locale], params[:id], preview_token: token
+           when 'KnowledgeBaseAnswer'
+             category_id = KnowledgeBase::Answer.find(params[:id]).category_id
+             help_answer_path params[:locale], category_id, params[:id], preview_token: token
+           end
+
+    redirect_to custom_path_if_needed(path, KnowledgeBase.first, full: true)
+  end
+
   private
 
   def assets(answer_translation_content_ids = nil)

+ 16 - 3
app/policies/controllers/attachments_controller_policy.rb

@@ -2,7 +2,7 @@
 
 class Controllers::AttachmentsControllerPolicy < Controllers::ApplicationControllerPolicy
   def show?
-    store_object_policy(store_object_owner)&.show?
+    store_object_policy(store_object_owner, allow_kb_preview_token: true)&.show?
   end
 
   def destroy?
@@ -30,8 +30,21 @@ class Controllers::AttachmentsControllerPolicy < Controllers::ApplicationControl
       &.safe_constantize
   end
 
-  def store_object_policy(target)
-    Pundit.policy user, target
+  def store_object_policy(target, allow_kb_preview_token: false)
+    if allow_kb_preview_token &&
+       attached_to_kb?(target) &&
+       (token = record.session[:kb_preview_token])
+      token_user = Token.check action: 'KnowledgeBasePreview', token: token
+    end
+
+    Pundit.policy token_user || user, target
+  end
+
+  def attached_to_kb?(target)
+    return true if target.is_a?(KnowledgeBase::Answer::Translation::Content)
+    return true if target.is_a?(KnowledgeBase::Answer)
+
+    false
   end
 
   def store_object_owner

+ 2 - 0
config/routes/knowledge_base.rb

@@ -24,6 +24,8 @@ Zammad::Application.routes.draw do
         post :search,         controller: 'knowledge_base/search'
         get  :recent_answers, controller: 'knowledge_base/answers'
 
+        get 'preview/:object/:id/:locale', to: 'knowledge_bases#preview'
+
         resources :manage, controller: 'knowledge_base/manage' do
           collection do
             get :init

+ 24 - 0
spec/policies/controllers/attachments_controller_policy_spec.rb

@@ -10,6 +10,7 @@ describe Controllers::AttachmentsControllerPolicy do
   let(:record_class) { AttachmentsController }
   let(:object)       { create(:knowledge_base_answer, visibility, :with_attachment, category: category) }
   let(:params)       { { id: object.attachments.first.id } }
+  let(:session)      { {} }
 
   let(:record) do
     rec             = record_class.new
@@ -19,6 +20,10 @@ describe Controllers::AttachmentsControllerPolicy do
     rec
   end
 
+  before do
+    allow(record).to receive(:session).and_return(session)
+  end
+
   context 'with no user' do
     let(:user) { nil }
 
@@ -67,4 +72,23 @@ describe Controllers::AttachmentsControllerPolicy do
       it { is_expected.to forbid_actions :show, :destroy }
     end
   end
+
+  context 'with a preview token' do
+    let(:user)       { false }
+    let(:visibility) { :draft }
+    let(:session)    { { kb_preview_token: token } }
+
+    context 'when token is valid' do
+      let(:token) { Token.renew_token! 'KnowledgeBasePreview', create(:admin).id }
+
+      it { is_expected.to permit_actions :show }
+      it { is_expected.to forbid_actions :destroy }
+    end
+
+    context 'when token user does not have access' do
+      let(:token) { Token.renew_token! 'KnowledgeBasePreview', create(:customer).id }
+
+      it { is_expected.to forbid_actions :show, :destroy }
+    end
+  end
 end

+ 22 - 0
spec/system/knowledge_base/locale/answer/edit_spec.rb

@@ -176,4 +176,26 @@ RSpec.describe 'Knowledge Base Locale Answer Edit', type: :system do
       end
     end
   end
+
+  describe 'previewing' do
+    before do
+      visit "#knowledge_base/#{knowledge_base.id}/locale/#{primary_locale.system_locale.locale}/answer/#{draft_answer.id}/edit"
+    end
+
+    it 'opens preview' do
+      new_window = window_opened_by { click '.icon-external' }
+
+      within_window new_window do
+        within '.main--article' do
+          expect(page).to have_text(draft_answer.translations.first.title)
+        end
+      end
+    end
+
+    it 'creates a KB preview token' do
+      expect { click('.icon-external') }
+        .to change { Token.exists?(action: 'KnowledgeBasePreview') }
+        .to(true)
+    end
+  end
 end

+ 13 - 1
spec/system/knowledge_base/locale/answer/reader_spec.rb

@@ -160,7 +160,7 @@ RSpec.describe 'Knowledge Base Locale Answer Reader', time_zone: 'Europe/London'
   end
 
   context 'when logged in as reader', authenticated: -> { visitor }, current_user_id: -> { editor.id } do
-    let(:editor) { create(:admin, firstname: 'Editor') }
+    let(:editor)  { create(:admin, firstname: 'Editor') }
     let(:visitor) { create(:agent) }
 
     it 'state not shown' do
@@ -190,6 +190,18 @@ RSpec.describe 'Knowledge Base Locale Answer Reader', time_zone: 'Europe/London'
         expect(page).to have_text editor.fullname
       end
     end
+
+    it 'opens preview' do
+      open_answer published_answer
+
+      new_window = window_opened_by { click '.icon-external' }
+
+      within_window new_window do
+        within '.main--article' do
+          expect(page).to have_text(published_answer.translations.first.title)
+        end
+      end
+    end
   end
 
   def open_answer(answer, locale: primary_locale)

+ 24 - 0
spec/system/knowledge_base_public/guest_spec.rb

@@ -71,6 +71,30 @@ RSpec.describe 'Public Knowledge Base for guest', authenticated_as: false, type:
     end
   end
 
+  context 'preview token' do
+    context 'when token is valid' do
+      let(:token) { Token.renew_token! 'KnowledgeBasePreview', create(:admin).id }
+
+      it 'loads draft answer' do
+        visit help_answer_path(primary_locale.system_locale.locale, category, draft_answer, preview_token: token)
+
+        within '.main--article' do
+          expect(page).to have_text(draft_answer.translations.first.title)
+        end
+      end
+    end
+
+    context 'when token user does not have access' do
+      let(:token) { Token.renew_token! 'KnowledgeBasePreview', create(:customer).id }
+
+      it 'loads draft answer' do
+        visit help_answer_path(primary_locale.system_locale.locale, category, draft_answer, preview_token: token)
+
+        expect(page).to have_no_text(draft_answer.translations.first.title)
+      end
+    end
+  end
+
   context 'wrong locale' do
     before { visit help_root_path(alternative_locale.system_locale.locale) }