1# frozen_string_literal: true
2
3module Gitlab
4  module BackgroundMigration
5    # corrects stored pages access level on db depending on project visibility
6    class FixPagesAccessLevel
7      # Copy routable here to avoid relying on application logic
8      module Routable
9        def build_full_path
10          if parent && path
11            parent.build_full_path + '/' + path
12          else
13            path
14          end
15        end
16      end
17
18      # Namespace
19      class Namespace < ActiveRecord::Base
20        self.table_name = 'namespaces'
21        self.inheritance_column = :_type_disabled
22
23        include Routable
24
25        belongs_to :parent, class_name: "Namespace"
26      end
27
28      # Project
29      class Project < ActiveRecord::Base
30        self.table_name = 'projects'
31        self.inheritance_column = :_type_disabled
32
33        include Routable
34
35        belongs_to :namespace
36        alias_method :parent, :namespace
37        alias_attribute :parent_id, :namespace_id
38
39        PRIVATE = 0
40        INTERNAL = 10
41        PUBLIC = 20
42
43        def pages_deployed?
44          Dir.exist?(public_pages_path)
45        end
46
47        def public_pages_path
48          File.join(pages_path, 'public')
49        end
50
51        def pages_path
52          # TODO: when we migrate Pages to work with new storage types, change here to use disk_path
53          File.join(Settings.pages.path, build_full_path)
54        end
55      end
56
57      # ProjectFeature
58      class ProjectFeature < ActiveRecord::Base
59        include ::EachBatch
60
61        self.table_name = 'project_features'
62
63        belongs_to :project
64
65        PRIVATE = 10
66        ENABLED = 20
67        PUBLIC  = 30
68      end
69
70      def perform(start_id, stop_id)
71        fix_public_access_level(start_id, stop_id)
72
73        make_internal_projects_public(start_id, stop_id)
74
75        fix_private_access_level(start_id, stop_id)
76      end
77
78      private
79
80      def access_control_is_enabled
81        @access_control_is_enabled = Gitlab.config.pages.access_control
82      end
83
84      # Public projects are allowed to have only enabled pages_access_level
85      # which is equivalent to public
86      def fix_public_access_level(start_id, stop_id)
87        project_features(start_id, stop_id, ProjectFeature::PUBLIC, Project::PUBLIC).each_batch do |features|
88          features.update_all(pages_access_level: ProjectFeature::ENABLED)
89        end
90      end
91
92      # If access control is disabled and project has pages deployed
93      # project will become unavailable when access control will become enabled
94      # we make these projects public to avoid negative surprise to user
95      def make_internal_projects_public(start_id, stop_id)
96        return if access_control_is_enabled
97
98        project_features(start_id, stop_id, ProjectFeature::ENABLED, Project::INTERNAL).find_each do |project_feature|
99          next unless project_feature.project.pages_deployed?
100
101          project_feature.update(pages_access_level: ProjectFeature::PUBLIC)
102        end
103      end
104
105      # Private projects are not allowed to have enabled access level, only `private` and `public`
106      # If access control is enabled, these projects currently behave as if they have `private` pages_access_level
107      # if access control is disabled, these projects currently behave as if they have `public` pages_access_level
108      # so we preserve this behaviour for projects with pages already deployed
109      # for project without pages we always set `private` access_level
110      def fix_private_access_level(start_id, stop_id)
111        project_features(start_id, stop_id, ProjectFeature::ENABLED, Project::PRIVATE).find_each do |project_feature|
112          if access_control_is_enabled
113            project_feature.update!(pages_access_level: ProjectFeature::PRIVATE)
114          else
115            fixed_access_level = project_feature.project.pages_deployed? ? ProjectFeature::PUBLIC : ProjectFeature::PRIVATE
116            project_feature.update!(pages_access_level: fixed_access_level)
117          end
118        end
119      end
120
121      def project_features(start_id, stop_id, pages_access_level, project_visibility_level)
122        ProjectFeature.where(id: start_id..stop_id).joins(:project)
123          .where(pages_access_level: pages_access_level)
124          .where(projects: { visibility_level: project_visibility_level })
125      end
126    end
127  end
128end
129