git_access.rb 7.6 KB
Newer Older
1 2
# Check a user's access to perform a git action. All public methods in this
# class return an instance of `GitlabAccessStatus`
3 4
module Gitlab
  class GitAccess
5
    UnauthorizedError = Class.new(StandardError)
6
    NotFoundError = Class.new(StandardError)
7
    ProjectMovedError = Class.new(NotFoundError)
8 9 10 11

    ERROR_MESSAGES = {
      upload: 'You are not allowed to upload code for this project.',
      download: 'You are not allowed to download code from this project.',
12 13
      deploy_key_upload:
        'This deploy key does not have write access to this project.',
M
Michael Kozono 已提交
14 15 16
      no_repo: 'A repository for this project does not exist yet.',
      project_not_found: 'The project you were looking for could not be found.',
      account_blocked: 'Your account has been blocked.',
17
      command_not_allowed: "The command you're trying to execute is not allowed.",
M
Michael Kozono 已提交
18
      upload_pack_disabled_over_http: 'Pulling over HTTP is not allowed.',
19
      receive_pack_disabled_over_http: 'Pushing over HTTP is not allowed.',
T
Toon Claes 已提交
20 21
      read_only: 'The repository is temporarily read-only. Please try again later.',
      cannot_push_to_read_only: "You can't push code to a read-only GitLab instance."
D
Douwe Maan 已提交
22
    }.freeze
23

D
Douwe Maan 已提交
24 25
    DOWNLOAD_COMMANDS = %w{ git-upload-pack git-upload-archive }.freeze
    PUSH_COMMANDS = %w{ git-receive-pack }.freeze
26
    ALL_COMMANDS = DOWNLOAD_COMMANDS + PUSH_COMMANDS
27

28
    attr_reader :actor, :project, :protocol, :authentication_abilities, :redirected_path
29

30
    def initialize(actor, project, protocol, authentication_abilities:, redirected_path: nil)
31 32
      @actor    = actor
      @project  = project
33
      @protocol = protocol
34
      @redirected_path = redirected_path
35
      @authentication_abilities = authentication_abilities
36 37
    end

J
Jacob Vosmaer 已提交
38
    def check(cmd, changes)
39
      check_protocol!
N
Nick Thomas 已提交
40
      check_valid_actor!
41
      check_active_user!
42
      check_project_accessibility!
43
      check_project_moved!
44
      check_command_disabled!(cmd)
45
      check_command_existence!(cmd)
46
      check_repository_existence!
D
Douwe Maan 已提交
47

48 49
      case cmd
      when *DOWNLOAD_COMMANDS
50
        check_download_access!
51
      when *PUSH_COMMANDS
52
        check_push_access!(changes)
53
      end
54

55
      true
56 57
    end

58
    def guest_can_download_code?
59 60 61
      Guest.can?(:download_code, project)
    end

62
    def user_can_download_code?
63
      authentication_abilities.include?(:download_code) && user_access.can_do_action?(:download_code)
64 65
    end

66
    def build_can_download_code?
67
      authentication_abilities.include?(:build_download_code) && user_access.can_do_action?(:build_download_code)
68 69
    end

70 71 72 73
    def protocol_allowed?
      Gitlab::ProtocolAccess.allowed?(protocol)
    end

74 75
    private

76 77 78 79 80 81 82 83
    def check_valid_actor!
      return unless actor.is_a?(Key)

      unless actor.valid?
        raise UnauthorizedError, "Your SSH key #{actor.errors[:key].first}."
      end
    end

84 85 86 87 88 89 90
    def check_protocol!
      unless protocol_allowed?
        raise UnauthorizedError, "Git access over #{protocol.upcase} is not allowed"
      end
    end

    def check_active_user!
91 92
      return if deploy_key?

93
      if user && !user_access.allowed?
M
Michael Kozono 已提交
94
        raise UnauthorizedError, ERROR_MESSAGES[:account_blocked]
95 96 97 98 99
      end
    end

    def check_project_accessibility!
      if project.blank? || !can_read_project?
100
        raise NotFoundError, ERROR_MESSAGES[:project_not_found]
101 102 103
      end
    end

104
    def check_project_moved!
105
      return if redirected_path.nil?
106

107
      project_moved = Checks::ProjectMoved.new(project, user, redirected_path, protocol)
108

109 110 111 112 113
      if project_moved.permanent_redirect?
        project_moved.add_redirect_message
      else
        raise ProjectMovedError, project_moved.redirect_message(rejected: true)
      end
114 115
    end

116
    def check_command_disabled!(cmd)
M
Michael Kozono 已提交
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132
      if upload_pack?(cmd)
        check_upload_pack_disabled!
      elsif receive_pack?(cmd)
        check_receive_pack_disabled!
      end
    end

    def check_upload_pack_disabled!
      if http? && upload_pack_disabled_over_http?
        raise UnauthorizedError, ERROR_MESSAGES[:upload_pack_disabled_over_http]
      end
    end

    def check_receive_pack_disabled!
      if http? && receive_pack_disabled_over_http?
        raise UnauthorizedError, ERROR_MESSAGES[:receive_pack_disabled_over_http]
133 134 135
      end
    end

136 137
    def check_command_existence!(cmd)
      unless ALL_COMMANDS.include?(cmd)
M
Michael Kozono 已提交
138
        raise UnauthorizedError, ERROR_MESSAGES[:command_not_allowed]
139 140 141
      end
    end

142 143 144 145 146 147
    def check_repository_existence!
      unless project.repository.exists?
        raise UnauthorizedError, ERROR_MESSAGES[:no_repo]
      end
    end

148 149 150 151
    def check_download_access!
      return if deploy_key?

      passed = user_can_download_code? ||
L
Lin Jen-Shin 已提交
152 153
        build_can_download_code? ||
        guest_can_download_code?
154 155 156 157 158 159 160

      unless passed
        raise UnauthorizedError, ERROR_MESSAGES[:download]
      end
    end

    def check_push_access!(changes)
161
      if project.repository_read_only?
T
Toon Claes 已提交
162 163 164 165
        raise UnauthorizedError, ERROR_MESSAGES[:read_only]
      end

      if Gitlab::Database.read_only?
J
James Lopez 已提交
166
        raise UnauthorizedError, push_to_read_only_message
167 168
      end

169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
      if deploy_key
        check_deploy_key_push_access!
      elsif user
        check_user_push_access!
      else
        raise UnauthorizedError, ERROR_MESSAGES[:upload]
      end

      return if changes.blank? # Allow access.

      check_change_access!(changes)
    end

    def check_user_push_access!
      unless authentication_abilities.include?(:push_code)
        raise UnauthorizedError, ERROR_MESSAGES[:upload]
      end
    end

    def check_deploy_key_push_access!
      unless deploy_key.can_push_to?(project)
        raise UnauthorizedError, ERROR_MESSAGES[:deploy_key_upload]
      end
    end

194 195 196 197 198
    def check_change_access!(changes)
      changes_list = Gitlab::ChangesList.new(changes)

      # Iterate over all changes to find if user allowed all of them to be applied
      changes_list.each do |change|
199 200 201
        # If user does not have access to make at least one change, cancel all
        # push by allowing the exception to bubble up
        check_single_change_access(change)
202 203 204
      end
    end

205 206
    def check_single_change_access(change)
      Checks::ChangeAccess.new(
207 208 209
        change,
        user_access: user_access,
        project: project,
210 211 212
        skip_authorization: deploy_key?,
        protocol: protocol
      ).exec
213 214
    end

215
    def deploy_key
216 217 218 219 220
      actor if deploy_key?
    end

    def deploy_key?
      actor.is_a?(DeployKey)
221
    end
222

223 224 225 226
    def ci?
      actor == :ci
    end

227
    def can_read_project?
228
      if deploy_key?
229
        deploy_key.has_access_to?(project)
230
      elsif user
231
        user.can?(:read_project, project)
232 233
      elsif ci?
        true # allow CI (build without a user) for backwards compatibility
234
      end || Guest.can?(:read_project, project)
235 236
    end

237 238 239 240 241 242 243 244 245 246 247 248
    def http?
      protocol == 'http'
    end

    def upload_pack?(command)
      command == 'git-upload-pack'
    end

    def receive_pack?(command)
      command == 'git-receive-pack'
    end

M
Michael Kozono 已提交
249 250 251 252 253 254 255 256
    def upload_pack_disabled_over_http?
      !Gitlab.config.gitlab_shell.upload_pack
    end

    def receive_pack_disabled_over_http?
      !Gitlab.config.gitlab_shell.receive_pack
    end

257 258
    protected

259 260 261 262 263 264 265 266
    def user
      return @user if defined?(@user)

      @user =
        case actor
        when User
          actor
        when Key
M
Michael Kozono 已提交
267
          actor.user unless actor.is_a?(DeployKey)
268 269
        when :ci
          nil
270 271
        end
    end
272 273 274 275 276 277 278 279

    def user_access
      @user_access ||= if ci?
                         CiAccess.new
                       else
                         UserAccess.new(user, project: project)
                       end
    end
J
James Lopez 已提交
280 281 282 283

    def push_to_read_only_message
      ERROR_MESSAGES[:cannot_push_to_read_only]
    end
284 285
  end
end