git_access.rb 6.7 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 8 9 10

    ERROR_MESSAGES = {
      upload: 'You are not allowed to upload code for this project.',
      download: 'You are not allowed to download code from this project.',
11 12
      deploy_key_upload:
        'This deploy key does not have write access to this project.',
M
Michael Kozono 已提交
13 14 15
      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.',
16
      command_not_allowed: "The command you're trying to execute is not allowed.",
M
Michael Kozono 已提交
17 18
      upload_pack_disabled_over_http: 'Pulling over HTTP is not allowed.',
      receive_pack_disabled_over_http: 'Pushing over HTTP is not allowed.'
D
Douwe Maan 已提交
19
    }.freeze
20

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

25
    attr_reader :actor, :project, :protocol, :user_access, :authentication_abilities
26

27
    def initialize(actor, project, protocol, authentication_abilities:)
28 29
      @actor    = actor
      @project  = project
30
      @protocol = protocol
31
      @authentication_abilities = authentication_abilities
32 33 34 35 36
      @user_access = if ci?
                       CiAccess.new
                     else
                       UserAccess.new(user, project: project)
                     end
37 38
    end

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

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

      build_status_object(true)
55 56
    end

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

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

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

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

73 74
    private

75 76 77 78 79 80 81
    def check_protocol!
      unless protocol_allowed?
        raise UnauthorizedError, "Git access over #{protocol.upcase} is not allowed"
      end
    end

    def check_active_user!
82 83
      return if deploy_key?

84
      if user && !user_access.allowed?
M
Michael Kozono 已提交
85
        raise UnauthorizedError, ERROR_MESSAGES[:account_blocked]
86 87 88 89 90
      end
    end

    def check_project_accessibility!
      if project.blank? || !can_read_project?
91
        raise NotFoundError, ERROR_MESSAGES[:project_not_found]
92 93 94
      end
    end

95
    def check_command_disabled!(cmd)
M
Michael Kozono 已提交
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
      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]
112 113 114
      end
    end

115 116
    def check_command_existence!(cmd)
      unless ALL_COMMANDS.include?(cmd)
M
Michael Kozono 已提交
117
        raise UnauthorizedError, ERROR_MESSAGES[:command_not_allowed]
118 119 120
      end
    end

121 122 123 124 125 126
    def check_repository_existence!
      unless project.repository.exists?
        raise UnauthorizedError, ERROR_MESSAGES[:no_repo]
      end
    end

127 128 129 130
    def check_download_access!
      return if deploy_key?

      passed = user_can_download_code? ||
L
Lin Jen-Shin 已提交
131 132
        build_can_download_code? ||
        guest_can_download_code?
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164

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

    def check_push_access!(changes)
      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

165 166 167 168 169
    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|
170
        status = check_single_change_access(change)
171 172 173 174 175 176 177
        unless status.allowed?
          # If user does not have access to make at least one change - cancel all push
          raise UnauthorizedError, status.message
        end
      end
    end

178 179
    def check_single_change_access(change)
      Checks::ChangeAccess.new(
180 181 182
        change,
        user_access: user_access,
        project: project,
183 184 185
        skip_authorization: deploy_key?,
        protocol: protocol
      ).exec
186 187
    end

188 189
    def matching_merge_request?(newrev, branch_name)
      Checks::MatchingMergeRequest.new(newrev, branch_name, project).match?
190 191
    end

192
    def deploy_key
193 194 195 196 197
      actor if deploy_key?
    end

    def deploy_key?
      actor.is_a?(DeployKey)
198
    end
199

200 201 202 203
    def ci?
      actor == :ci
    end

204
    def can_read_project?
205
      if deploy_key?
206
        deploy_key.has_access_to?(project)
207
      elsif user
208
        user.can?(:read_project, project)
209 210
      elsif ci?
        true # allow CI (build without a user) for backwards compatibility
211
      end || Guest.can?(:read_project, project)
212 213
    end

214 215 216 217 218 219 220 221 222 223 224 225
    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 已提交
226 227 228 229 230 231 232 233
    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

234 235
    protected

236 237 238 239 240 241 242 243
    def user
      return @user if defined?(@user)

      @user =
        case actor
        when User
          actor
        when Key
M
Michael Kozono 已提交
244
          actor.user unless actor.is_a?(DeployKey)
245 246
        when :ci
          nil
247 248 249
        end
    end

250 251
    def build_status_object(status)
      Gitlab::GitAccessStatus.new(status)
252
    end
253 254
  end
end