change_access.rb 6.93 KB
Newer Older
1
module Gitlab
2 3
  module Checks
    class ChangeAccess
4
      prepend EE::Gitlab::Checks::ChangeAccess
Valery Sizov's avatar
Valery Sizov committed
5

6 7 8 9
      ERROR_MESSAGES = {
        push_code: 'You are not allowed to push code to this project.',
        delete_default_branch: 'The default branch of a project cannot be deleted.',
        force_push_protected_branch: 'You are not allowed to force push code to a protected branch on this project.',
Mark Chao's avatar
Mark Chao committed
10
        non_master_delete_protected_branch: 'You are not allowed to delete protected branches from this project. Only a project maintainer or owner can delete a protected branch.',
11 12 13 14 15 16
        non_web_delete_protected_branch: 'You can only delete protected branches using the web interface.',
        merge_protected_branch: 'You are not allowed to merge code into protected branches on this project.',
        push_protected_branch: 'You are not allowed to push code to protected branches on this project.',
        change_existing_tags: 'You are not allowed to change existing tags on this project.',
        update_protected_tag: 'Protected tags cannot be updated.',
        delete_protected_tag: 'Protected tags cannot be deleted.',
17
        create_protected_tag: 'You are not allowed to create this tag as it is protected.',
18
        lfs_objects_missing: 'LFS objects are missing. Ensure LFS is properly set up or try a manual "git lfs push --all".'
19 20
      }.freeze

21
      attr_reader :user_access, :project, :skip_authorization, :skip_lfs_integrity_check, :protocol, :oldrev, :newrev, :ref, :branch_name, :tag_name
22

23
      def initialize(
24
        change, user_access:, project:, skip_authorization: false,
25
        skip_lfs_integrity_check: false, protocol:
26
      )
27 28
        @oldrev, @newrev, @ref = change.values_at(:oldrev, :newrev, :ref)
        @branch_name = Gitlab::Git.branch_name(@ref)
29
        @tag_name = Gitlab::Git.tag_name(@ref)
30 31
        @user_access = user_access
        @project = project
32
        @skip_authorization = skip_authorization
33
        @skip_lfs_integrity_check = skip_lfs_integrity_check
34
        @protocol = protocol
35 36
      end

37
      def exec(skip_commits_check: false)
38
        return true if skip_authorization
39

40 41 42
        push_checks
        branch_checks
        tag_checks
43
        lfs_objects_exist_check unless skip_lfs_integrity_check
44
        commits_check unless skip_commits_check
45

46
        true
47 48 49 50
      end

      protected

51
      def push_checks
52
        unless can_push?
53
          raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:push_code]
54 55 56 57
        end
      end

      def branch_checks
58
        return unless branch_name
59

60
        if deletion? && branch_name == project.default_branch
61
          raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:delete_default_branch]
62 63 64 65 66 67
        end

        protected_branch_checks
      end

      def protected_branch_checks
68
        return unless ProtectedBranch.protected?(project, branch_name)
69

70
        if forced_push?
71
          raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:force_push_protected_branch]
72 73
        end

74 75 76 77 78 79 80 81
        if deletion?
          protected_branch_deletion_checks
        else
          protected_branch_push_checks
        end
      end

      def protected_branch_deletion_checks
82
        unless user_access.can_delete_branch?(branch_name)
83
          raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:non_master_delete_protected_branch]
84 85
        end

86
        unless updated_from_web?
87
          raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:non_web_delete_protected_branch]
88 89 90 91
        end
      end

      def protected_branch_push_checks
92
        if matching_merge_request?
93
          unless user_access.can_merge_to_branch?(branch_name) || user_access.can_push_to_branch?(branch_name)
94
            raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:merge_protected_branch]
95 96
          end
        else
97
          unless user_access.can_push_to_branch?(branch_name)
98
            raise GitAccess::UnauthorizedError, push_to_protected_branch_rejected_message
99 100 101 102 103
          end
        end
      end

      def tag_checks
104
        return unless tag_name
105

106
        if tag_exists? && user_access.cannot_do_action?(:admin_project)
107
          raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:change_existing_tags]
108
        end
109 110 111 112 113

        protected_tag_checks
      end

      def protected_tag_checks
114
        return unless ProtectedTag.protected?(project, tag_name)
115

116 117
        raise(GitAccess::UnauthorizedError, ERROR_MESSAGES[:update_protected_tag]) if update?
        raise(GitAccess::UnauthorizedError, ERROR_MESSAGES[:delete_protected_tag]) if deletion?
118

119
        unless user_access.can_create_tag?(tag_name)
120
          raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:create_protected_tag]
121 122 123
        end
      end

124 125
      def commits_check
        return if deletion? || newrev.nil?
126
        return unless should_run_commit_validations?
127

128 129 130 131 132
        # n+1: https://gitlab.com/gitlab-org/gitlab-ee/issues/3593
        ::Gitlab::GitalyClient.allow_n_plus_1_calls do
          commits.each do |commit|
            commit_check.validate(commit, validations_for_commit(commit))
          end
133
        end
134 135

        commit_check.validate_file_paths
136 137
      end

138 139
      # Method overwritten in EE to inject custom validations
      def validations_for_commit(_)
140
        []
141 142
      end

143 144
      private

145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
      def push_to_protected_branch_rejected_message
        if project.empty_repo?
          empty_project_push_message
        else
          ERROR_MESSAGES[:push_protected_branch]
        end
      end

      def empty_project_push_message
        <<~MESSAGE

        A default branch (e.g. master) does not yet exist for #{project.full_path}
        Ask a project Owner or Maintainer to create a default branch:

          #{project_members_url}

        MESSAGE
      end

      def project_members_url
        Gitlab::Routing.url_helpers.project_project_members_url(project)
      end

168 169 170 171
      def should_run_commit_validations?
        commit_check.validate_lfs_file_locks?
      end

172 173 174 175
      def updated_from_web?
        protocol == 'web'
      end

176
      def tag_exists?
177
        project.repository.tag_exists?(tag_name)
178 179 180
      end

      def forced_push?
181
        Gitlab::Checks::ForcePush.force_push?(project, oldrev, newrev)
182 183
      end

184
      def update?
185
        !Gitlab::Git.blank_ref?(oldrev) && !deletion?
186 187 188
      end

      def deletion?
189
        Gitlab::Git.blank_ref?(newrev)
190 191
      end

192
      def matching_merge_request?
193
        Checks::MatchingMergeRequest.new(newrev, branch_name, project).match?
194 195
      end

196
      def lfs_objects_exist_check
197
        lfs_check = Checks::LfsIntegrity.new(project, newrev)
198 199 200 201 202

        if lfs_check.objects_missing?
          raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:lfs_objects_missing]
        end
      end
203

204 205 206 207
      def commit_check
        @commit_check ||= Gitlab::Checks::CommitCheck.new(project, user_access.user, newrev, oldrev)
      end

208
      def commits
209
        @commits ||= project.repository.new_commits(newrev)
210
      end
211 212 213 214 215

      def can_push?
        user_access.can_do_action?(:push_code) ||
          user_access.can_push_to_branch?(branch_name)
      end
216 217 218
    end
  end
end