Browse Source

Refactoring: Migrate user permission handling to Pundit policies.

Ryan Lue 3 years ago
parent
commit
c23e174ee0

+ 3 - 9
app/controllers/application_controller/handles_devices.rb

@@ -4,22 +4,16 @@ module ApplicationController::HandlesDevices
   extend ActiveSupport::Concern
   extend ActiveSupport::Concern
 
 
   included do
   included do
-    before_action :user_device_check
+    before_action :user_device_log
   end
   end
 
 
-  def user_device_check
-    return false if !user_device_log(current_user, 'session')
-
-    true
-  end
-
-  def user_device_log(user, type)
+  def user_device_log(user = current_user, type = 'session')
     switched_from_user_id = ENV['SWITCHED_FROM_USER_ID'] || session[:switched_from_user_id]
     switched_from_user_id = ENV['SWITCHED_FROM_USER_ID'] || session[:switched_from_user_id]
     return true if params[:controller] == 'init' # do no device logging on static initial page
     return true if params[:controller] == 'init' # do no device logging on static initial page
     return true if switched_from_user_id
     return true if switched_from_user_id
     return true if current_user_on_behalf # do no device logging for the user on behalf feature
     return true if current_user_on_behalf # do no device logging for the user on behalf feature
     return true if !user
     return true if !user
-    return true if !user.permissions?('user_preferences.device')
+    return true if !policy(UserDevice).log?
     return true if type == 'SSO'
     return true if type == 'SSO'
 
 
     time_to_check = true
     time_to_check = true

+ 1 - 5
app/controllers/application_controller/handles_errors.rb

@@ -102,11 +102,7 @@ module ApplicationController::HandlesErrors
 
 
     if data[:error_human].present?
     if data[:error_human].present?
       data[:error] = data[:error_human]
       data[:error] = data[:error_human]
-    elsif !current_user&.permissions?('admin')
-      # We want to avoid leaking of internal information but also want the user
-      # to give the administrator a reference to find the cause of the error.
-      # Therefore we generate a one time unique error ID that can be used to
-      # search the logs and find the actual error message.
+    elsif !policy(Exceptions).view_details?
       error_code_prefix = "Error ID #{SecureRandom.urlsafe_base64(6)}:"
       error_code_prefix = "Error ID #{SecureRandom.urlsafe_base64(6)}:"
       Rails.logger.error "#{error_code_prefix} #{data[:error]}"
       Rails.logger.error "#{error_code_prefix} #{data[:error]}"
       data[:error] = "#{error_code_prefix} Please contact your administrator."
       data[:error] = "#{error_code_prefix} Please contact your administrator."

+ 11 - 39
app/controllers/application_controller/has_user.rb

@@ -10,10 +10,7 @@ module ApplicationController::HasUser
   private
   private
 
 
   def current_user
   def current_user
-    user_on_behalf = current_user_on_behalf
-    return user_on_behalf if user_on_behalf
-
-    current_user_real
+    current_user_on_behalf || current_user_real
   end
   end
 
 
   # Finds the User with the ID stored in the session with the key
   # Finds the User with the ID stored in the session with the key
@@ -21,10 +18,7 @@ module ApplicationController::HasUser
   # a Rails application; logging in sets the session value and
   # a Rails application; logging in sets the session value and
   # logging out removes it.
   # logging out removes it.
   def current_user_real
   def current_user_real
-    return @_current_user if @_current_user
-    return if !session[:user_id]
-
-    @_current_user = User.lookup(id: session[:user_id])
+    @_current_user ||= User.lookup(id: session[:user_id]) # rubocop:disable Naming/MemoizedInstanceVariableName
   end
   end
 
 
   # Finds the user based on the id, login or email which is given
   # Finds the user based on the id, login or email which is given
@@ -33,42 +27,24 @@ module ApplicationController::HasUser
   # to do changes with a user which is different from the admin user.
   # to do changes with a user which is different from the admin user.
   # E.g. create a ticket as a customer user based on a user with admin rights.
   # E.g. create a ticket as a customer user based on a user with admin rights.
   def current_user_on_behalf
   def current_user_on_behalf
-
-    # check header
-    return if request.headers['X-On-Behalf-Of'].blank?
-
-    # return user if set
-    return @_user_on_behalf if @_user_on_behalf
-
-    # get current user
-    user_real = current_user_real
-    return if !user_real
-
-    # check if the user has admin rights
-    raise Exceptions::Forbidden, "Current user has no permission to use 'X-On-Behalf-Of'!" if !user_real.permissions?('admin.user')
+    return if request.headers['X-On-Behalf-Of'].blank?  # require header
+    return @_user_on_behalf if @_user_on_behalf         # return memoized user
+    return if !current_user_real                        # require session user
+    if !SessionsPolicy.new(current_user_real, Sessions).impersonate?
+      raise Exceptions::Forbidden, "Current user has no permission to use 'X-On-Behalf-Of'!"
+    end
 
 
     # find user for execution based on the header
     # find user for execution based on the header
     %i[id login email].each do |field|
     %i[id login email].each do |field|
-      search_attributes = search_attributes(field)
-      @_user_on_behalf = User.find_by(search_attributes)
-      next if !@_user_on_behalf
+      @_user_on_behalf = User.find_by(field => request.headers['X-On-Behalf-Of'].to_s.downcase.strip)
 
 
-      return @_user_on_behalf
+      return @_user_on_behalf if @_user_on_behalf
     end
     end
 
 
     # no behalf of user found
     # no behalf of user found
     raise Exceptions::Forbidden, "No such user '#{request.headers['X-On-Behalf-Of']}'"
     raise Exceptions::Forbidden, "No such user '#{request.headers['X-On-Behalf-Of']}'"
   end
   end
 
 
-  def search_attributes(field)
-    search_attributes = {}
-    search_attributes[field] = request.headers['X-On-Behalf-Of']
-    if %i[login email].include?(field)
-      search_attributes[field] = search_attributes[field].to_s.downcase.strip
-    end
-    search_attributes
-  end
-
   def current_user_set(user, auth_type = 'session')
   def current_user_set(user, auth_type = 'session')
     session[:user_id] = user.id
     session[:user_id] = user.id
     @_auth_type = auth_type
     @_auth_type = auth_type
@@ -79,11 +55,7 @@ module ApplicationController::HasUser
   # Sets the current user into a named Thread location so that it can be accessed
   # Sets the current user into a named Thread location so that it can be accessed
   # by models and observers
   # by models and observers
   def set_user
   def set_user
-    if !current_user
-      UserInfo.current_user_id = 1
-      return
-    end
-    UserInfo.current_user_id = current_user.id
+    UserInfo.current_user_id = current_user&.id || 1
   end
   end
 
 
   # update session updated_at
   # update session updated_at

+ 5 - 5
app/controllers/attachments_controller.rb

@@ -1,9 +1,9 @@
 # Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
 # Copyright (C) 2012-2021 Zammad Foundation, http://zammad-foundation.org/
 
 
 class AttachmentsController < ApplicationController
 class AttachmentsController < ApplicationController
+  prepend_before_action :authorize!, only: %i[show destroy]
   prepend_before_action :authentication_check, except: %i[show destroy]
   prepend_before_action :authentication_check, except: %i[show destroy]
   prepend_before_action :authentication_check_only, only: %i[show destroy]
   prepend_before_action :authentication_check_only, only: %i[show destroy]
-  before_action :verify_object_permissions, only: %i[show destroy]
 
 
   def show
   def show
     content   = @file.content_preview if params[:preview] && @file.preferences[:content_preview]
     content   = @file.content_preview if params[:preview] && @file.preferences[:content_preview]
@@ -80,12 +80,12 @@ class AttachmentsController < ApplicationController
     raise Exceptions::Forbidden, "Invalid disposition #{disposition} requested. Only #{valid_disposition.join(', ')} are valid."
     raise Exceptions::Forbidden, "Invalid disposition #{disposition} requested. Only #{valid_disposition.join(', ')} are valid."
   end
   end
 
 
-  def verify_object_permissions
+  def authorize!
     @file = Store.find(params[:id])
     @file = Store.find(params[:id])
 
 
-    klass = @file&.store_object&.name&.safe_constantize
-    return if klass.send("can_#{params[:action]}_attachment?", @file, current_user)
-
+    record = @file&.store_object&.name&.safe_constantize&.find(@file.o_id)
+    authorize(record) if record
+  rescue Pundit::NotAuthorizedError
     raise ActiveRecord::RecordNotFound
     raise ActiveRecord::RecordNotFound
   end
   end
 end
 end

+ 1 - 1
app/controllers/form_controller.rb

@@ -6,7 +6,7 @@ class FormController < ApplicationController
   skip_before_action :verify_csrf_token
   skip_before_action :verify_csrf_token
   before_action :cors_preflight_check
   before_action :cors_preflight_check
   after_action :set_access_control_headers_execute
   after_action :set_access_control_headers_execute
-  skip_before_action :user_device_check
+  skip_before_action :user_device_log
 
 
   def configuration
   def configuration
     return if !fingerprint_exists?
     return if !fingerprint_exists?

+ 1 - 3
app/controllers/knowledge_base/public/answers_controller.rb

@@ -13,10 +13,8 @@ class KnowledgeBase::Public::AnswersController < KnowledgeBase::Public::BaseCont
   private
   private
 
 
   def render_alternative
   def render_alternative
-    @alternative = @knowledge_base
-                   .answers
+    @alternative = policy_scope(@knowledge_base.answers)
                    .eager_load(translations: :kb_locale)
                    .eager_load(translations: :kb_locale)
-                   .check_published_unless_editor(current_user)
                    .find_by(id: params[:answer])
                    .find_by(id: params[:answer])
 
 
     raise ActiveRecord::RecordNotFound if !@alternative&.translations&.any?
     raise ActiveRecord::RecordNotFound if !@alternative&.translations&.any?

+ 5 - 12
app/controllers/knowledge_base/public/base_controller.rb

@@ -2,7 +2,7 @@
 
 
 class KnowledgeBase::Public::BaseController < ApplicationController
 class KnowledgeBase::Public::BaseController < ApplicationController
   before_action :load_kb
   before_action :load_kb
-  helper_method :system_locale_via_uri, :fallback_locale, :current_user, :editor?, :find_category, :filter_primary_kb_locale, :menu_items, :all_locales
+  helper_method :system_locale_via_uri, :fallback_locale, :current_user, :find_category, :filter_primary_kb_locale, :menu_items, :all_locales
 
 
   layout 'knowledge_base'
   layout 'knowledge_base'
 
 
@@ -11,8 +11,7 @@ class KnowledgeBase::Public::BaseController < ApplicationController
   private
   private
 
 
   def load_kb
   def load_kb
-    @knowledge_base = KnowledgeBase
-                      .check_active_unless_editor(current_user)
+    @knowledge_base = policy_scope(KnowledgeBase)
                       .localed(guess_locale_via_uri)
                       .localed(guess_locale_via_uri)
                       .first
                       .first
 
 
@@ -63,26 +62,20 @@ class KnowledgeBase::Public::BaseController < ApplicationController
     list
     list
       .localed(system_locale_via_uri)
       .localed(system_locale_via_uri)
       .sorted
       .sorted
-      .to_a
-      .select { |elem| elem.visible_content_for?(current_user) }
+      .select { |category| policy(category).show? }
   end
   end
 
 
   def find_answer(scope, id)
   def find_answer(scope, id)
     return if scope.nil?
     return if scope.nil?
 
 
-    scope
+    policy_scope(scope)
       .localed(system_locale_via_uri)
       .localed(system_locale_via_uri)
       .include_contents
       .include_contents
-      .check_published_unless_editor(current_user)
       .find_by(id: id)
       .find_by(id: id)
   end
   end
 
 
-  def editor?
-    current_user&.permissions? 'knowledge_base.editor'
-  end
-
   def not_found(e)
   def not_found(e)
-    @knowledge_base = KnowledgeBase.check_active_unless_editor(current_user).first
+    @knowledge_base = policy_scope(KnowledgeBase).first
 
 
     if @knowledge_base.nil?
     if @knowledge_base.nil?
       super
       super

+ 7 - 9
app/controllers/knowledge_base/public/categories_controller.rb

@@ -7,30 +7,28 @@ class KnowledgeBase::Public::CategoriesController < KnowledgeBase::Public::BaseC
     @categories     = categories_filter(@knowledge_base.categories.root)
     @categories     = categories_filter(@knowledge_base.categories.root)
     @object_locales = find_locales(@knowledge_base)
     @object_locales = find_locales(@knowledge_base)
 
 
-    raise ActiveRecord::RecordNotFound if !editor? && @categories.empty?
+    authorize(@categories, policy_class: KnowledgeBase::Public::CategoryPolicy)
+  rescue Pundit::NotAuthorizedError
+    raise ActiveRecord::RecordNotFound
   end
   end
 
 
   def show
   def show
     @object = find_category(params[:category])
     @object = find_category(params[:category])
 
 
-    render_alternatives && return if !@object&.visible_content_for?(current_user)
+    render_alternatives && return if @object.nil? || !policy(@object).show?
 
 
     @categories     = categories_filter(@object.children)
     @categories     = categories_filter(@object.children)
     @object_locales = find_locales(@object)
     @object_locales = find_locales(@object)
 
 
-    @answers = @object
-               .answers
+    @answers = policy_scope(@object.answers)
                .localed(system_locale_via_uri)
                .localed(system_locale_via_uri)
-               .check_published_unless_editor(current_user)
                .sorted
                .sorted
 
 
     render :index
     render :index
   end
   end
 
 
   def forward_root
   def forward_root
-    knowledge_base = KnowledgeBase
-                     .check_active_unless_editor(current_user)
-                     .first!
+    knowledge_base = policy_scope(KnowledgeBase).first!
 
 
     primary_locale = KnowledgeBase::Locale
     primary_locale = KnowledgeBase::Locale
                      .system_with_kb_locales(knowledge_base)
                      .system_with_kb_locales(knowledge_base)
@@ -54,7 +52,7 @@ class KnowledgeBase::Public::CategoriesController < KnowledgeBase::Public::BaseC
                    .eager_load(translations: :kb_locale)
                    .eager_load(translations: :kb_locale)
                    .find_by(id: params[:category])
                    .find_by(id: params[:category])
 
 
-    if !@alternative&.translations&.any? || !@alternative&.visible_content_for?(current_user)
+    if @alternative.nil? || @alternative.translations.none? || !policy(@alternative).show?
       raise ActiveRecord::RecordNotFound
       raise ActiveRecord::RecordNotFound
     end
     end
 
 

+ 1 - 1
app/controllers/sessions_controller.rb

@@ -3,7 +3,7 @@
 class SessionsController < ApplicationController
 class SessionsController < ApplicationController
   prepend_before_action -> { authentication_check && authorize! }, only: %i[switch_to_user list delete]
   prepend_before_action -> { authentication_check && authorize! }, only: %i[switch_to_user list delete]
   skip_before_action :verify_csrf_token, only: %i[show destroy create_omniauth failure_omniauth]
   skip_before_action :verify_csrf_token, only: %i[show destroy create_omniauth failure_omniauth]
-  skip_before_action :user_device_check, only: %i[create_sso]
+  skip_before_action :user_device_log, only: %i[create_sso]
 
 
   # "Create" a login, aka "log the user in"
   # "Create" a login, aka "log the user in"
   def create
   def create

+ 1 - 1
app/helpers/knowledge_base_visibility_class_helper.rb

@@ -2,7 +2,7 @@
 
 
 module KnowledgeBaseVisibilityClassHelper
 module KnowledgeBaseVisibilityClassHelper
   def visibility_class_name(object)
   def visibility_class_name(object)
-    return if !current_user&.permissions?('knowledge_base.editor')
+    return if !policy(:knowledge_base).edit?
 
 
     suffix = case object
     suffix = case object
              when CanBePublished
              when CanBePublished

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