Commit 6796dcf2 authored by Vladimir Shushlin's avatar Vladimir Shushlin 👆 Committed by Nick Thomas
Browse files

Fix wrong pages access level default

- Set access level in before_validation hook
- Add post migration for updating existing project_features
parent 9c3dfd20
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -86,6 +86,8 @@ def ensure_feature!(feature)
  default_value_for :wiki_access_level,           value: ENABLED, allows_nil: false
  default_value_for :repository_access_level,     value: ENABLED, allows_nil: false

  default_value_for(:pages_access_level, allows_nil: false) { |feature| feature.project&.public? ? ENABLED : PRIVATE }

  def feature_available?(feature, user)
    # This feature might not be behind a feature flag at all, so default to true
    return false unless ::Feature.enabled?(feature, user, default_enabled: true)
+28 −0
Original line number Diff line number Diff line
# frozen_string_literal: true

class FixWrongPagesAccessLevel < ActiveRecord::Migration[5.1]
  include Gitlab::Database::MigrationHelpers

  DOWNTIME = false

  MIGRATION = 'FixPagesAccessLevel'
  BATCH_SIZE = 20_000
  BATCH_TIME = 2.minutes

  disable_ddl_transaction!

  class ProjectFeature < ActiveRecord::Base
    include ::EachBatch

    self.table_name = 'project_features'
    self.inheritance_column = :_type_disabled
  end

  def up
    queue_background_migration_jobs_by_range_at_intervals(
      ProjectFeature,
      MIGRATION,
      BATCH_TIME,
      batch_size: BATCH_SIZE)
  end
end
+12 −0
Original line number Diff line number Diff line
# frozen_string_literal: true

class DropProjectFeaturesPagesAccessLevelDefault < ActiveRecord::Migration[5.1]
  include Gitlab::Database::MigrationHelpers

  DOWNTIME = false
  ENABLED_VALUE = 20

  def change
    change_column_default :project_features, :pages_access_level, from: ENABLED_VALUE, to: nil
  end
end
+2 −2
Original line number Diff line number Diff line
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 2019_07_03_130053) do
ActiveRecord::Schema.define(version: 2019_07_15_114644) do

  # These are extensions that must be enabled in order to support this database
  enable_extension "pg_trgm"
@@ -2507,7 +2507,7 @@
    t.datetime "created_at"
    t.datetime "updated_at"
    t.integer "repository_access_level", default: 20, null: false
    t.integer "pages_access_level", default: 20, null: false
    t.integer "pages_access_level", null: false
    t.index ["project_id"], name: "index_project_features_on_project_id", unique: true, using: :btree
  end

+128 −0
Original line number Diff line number Diff line
# frozen_string_literal: true

module Gitlab
  module BackgroundMigration
    # corrects stored pages access level on db depending on project visibility
    class FixPagesAccessLevel
      # Copy routable here to avoid relying on application logic
      module Routable
        def build_full_path
          if parent && path
            parent.build_full_path + '/' + path
          else
            path
          end
        end
      end

      # Namespace
      class Namespace < ApplicationRecord
        self.table_name = 'namespaces'
        self.inheritance_column = :_type_disabled

        include Routable

        belongs_to :parent, class_name: "Namespace"
      end

      # Project
      class Project < ActiveRecord::Base
        self.table_name = 'projects'
        self.inheritance_column = :_type_disabled

        include Routable

        belongs_to :namespace
        alias_method :parent, :namespace
        alias_attribute :parent_id, :namespace_id

        PRIVATE = 0
        INTERNAL = 10
        PUBLIC = 20

        def pages_deployed?
          Dir.exist?(public_pages_path)
        end

        def public_pages_path
          File.join(pages_path, 'public')
        end

        def pages_path
          # TODO: when we migrate Pages to work with new storage types, change here to use disk_path
          File.join(Settings.pages.path, build_full_path)
        end
      end

      # ProjectFeature
      class ProjectFeature < ActiveRecord::Base
        include ::EachBatch

        self.table_name = 'project_features'

        belongs_to :project

        PRIVATE = 10
        ENABLED = 20
        PUBLIC  = 30
      end

      def perform(start_id, stop_id)
        fix_public_access_level(start_id, stop_id)

        make_internal_projects_public(start_id, stop_id)

        fix_private_access_level(start_id, stop_id)
      end

      private

      def access_control_is_enabled
        @access_control_is_enabled = Gitlab.config.pages.access_control
      end

      # Public projects are allowed to have only enabled pages_access_level
      # which is equivalent to public
      def fix_public_access_level(start_id, stop_id)
        project_features(start_id, stop_id, ProjectFeature::PUBLIC, Project::PUBLIC).each_batch do |features|
          features.update_all(pages_access_level: ProjectFeature::ENABLED)
        end
      end

      # If access control is disabled and project has pages deployed
      # project will become unavailable when access control will become enabled
      # we make these projects public to avoid negative surprise to user
      def make_internal_projects_public(start_id, stop_id)
        return if access_control_is_enabled

        project_features(start_id, stop_id, ProjectFeature::ENABLED, Project::INTERNAL).find_each do |project_feature|
          next unless project_feature.project.pages_deployed?

          project_feature.update(pages_access_level: ProjectFeature::PUBLIC)
        end
      end

      # Private projects are not allowed to have enabled access level, only `private` and `public`
      # If access control is enabled, these projects currently behave as if the have `private` pages_access_level
      # if access control is disabled, these projects currently behave as if the have `public` pages_access_level
      # so we preserve this behaviour for projects with pages already deployed
      # for project without pages we always set `private` access_level
      def fix_private_access_level(start_id, stop_id)
        project_features(start_id, stop_id, ProjectFeature::ENABLED, Project::PRIVATE).find_each do |project_feature|
          if access_control_is_enabled
            project_feature.update!(pages_access_level: ProjectFeature::PRIVATE)
          else
            fixed_access_level = project_feature.project.pages_deployed? ? ProjectFeature::PUBLIC : ProjectFeature::PRIVATE
            project_feature.update!(pages_access_level: fixed_access_level)
          end
        end
      end

      def project_features(start_id, stop_id, pages_access_level, project_visibility_level)
        ProjectFeature.where(id: start_id..stop_id).joins(:project)
          .where(pages_access_level: pages_access_level)
          .where(projects: { visibility_level: project_visibility_level })
      end
    end
  end
end
Loading