BigW Consortium Gitlab

git_access.rb 5.2 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 6 7 8 9
    UnauthorizedError = Class.new(StandardError)

    ERROR_MESSAGES = {
      upload: 'You are not allowed to upload code for this project.',
      download: 'You are not allowed to download code from this project.',
10 11
      deploy_key_upload:
        'This deploy key does not have write access to this project.',
12
      no_repo: 'A repository for this project does not exist yet.'
13
    }.freeze
14

15 16
    DOWNLOAD_COMMANDS = %w{ git-upload-pack git-upload-archive }.freeze
    PUSH_COMMANDS = %w{ git-receive-pack }.freeze
17
    ALL_COMMANDS = DOWNLOAD_COMMANDS + PUSH_COMMANDS
18

19
    attr_reader :actor, :project, :protocol, :user_access, :authentication_abilities
20

21
    def initialize(actor, project, protocol, authentication_abilities:)
22 23
      @actor    = actor
      @project  = project
24
      @protocol = protocol
25
      @authentication_abilities = authentication_abilities
26
      @user_access = UserAccess.new(user, project: project)
27 28
    end

29
    def check(cmd, changes)
30
      check_protocol!
31
      check_active_user!
32 33
      check_project_accessibility!
      check_command_existence!(cmd)
34
      check_repository_existence!
35

36 37
      case cmd
      when *DOWNLOAD_COMMANDS
38
        check_download_access!
39
      when *PUSH_COMMANDS
40
        check_push_access!(changes)
41
      end
42 43 44 45

      build_status_object(true)
    rescue UnauthorizedError => ex
      build_status_object(false, ex.message)
46 47
    end

48
    def guest_can_download_code?
49 50 51
      Guest.can?(:download_code, project)
    end

52
    def user_can_download_code?
53
      authentication_abilities.include?(:download_code) && user_access.can_do_action?(:download_code)
54 55
    end

56
    def build_can_download_code?
57
      authentication_abilities.include?(:build_download_code) && user_access.can_do_action?(:build_download_code)
58 59
    end

60 61 62 63
    def protocol_allowed?
      Gitlab::ProtocolAccess.allowed?(protocol)
    end

64 65
    private

66 67 68 69 70 71 72
    def check_protocol!
      unless protocol_allowed?
        raise UnauthorizedError, "Git access over #{protocol.upcase} is not allowed"
      end
    end

    def check_active_user!
73 74
      return if deploy_key?

75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
      if user && !user_access.allowed?
        raise UnauthorizedError, "Your account has been blocked."
      end
    end

    def check_project_accessibility!
      if project.blank? || !can_read_project?
        raise UnauthorizedError, 'The project you were looking for could not be found.'
      end
    end

    def check_command_existence!(cmd)
      unless ALL_COMMANDS.include?(cmd)
        raise UnauthorizedError, "The command you're trying to execute is not allowed."
      end
    end

92 93 94 95 96 97
    def check_repository_existence!
      unless project.repository.exists?
        raise UnauthorizedError, ERROR_MESSAGES[:no_repo]
      end
    end

98 99 100 101
    def check_download_access!
      return if deploy_key?

      passed = user_can_download_code? ||
102 103
        build_can_download_code? ||
        guest_can_download_code?
104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135

      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

136 137 138 139 140
    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|
141
        status = check_single_change_access(change)
142 143 144 145 146 147 148
        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

149 150
    def check_single_change_access(change)
      Checks::ChangeAccess.new(
151 152 153
        change,
        user_access: user_access,
        project: project,
154 155 156
        skip_authorization: deploy_key?,
        protocol: protocol
      ).exec
157 158
    end

159 160
    def matching_merge_request?(newrev, branch_name)
      Checks::MatchingMergeRequest.new(newrev, branch_name, project).match?
161 162
    end

163
    def deploy_key
164 165 166 167 168
      actor if deploy_key?
    end

    def deploy_key?
      actor.is_a?(DeployKey)
169
    end
170

171
    def can_read_project?
172
      if deploy_key
173
        deploy_key.has_access_to?(project)
174
      elsif user
175 176
        user.can?(:read_project, project)
      end || Guest.can?(:read_project, project)
177 178
    end

179 180
    protected

181 182 183 184 185 186 187
    def user
      return @user if defined?(@user)

      @user =
        case actor
        when User
          actor
188 189
        when DeployKey
          nil
190 191 192 193 194
        when Key
          actor.user
        end
    end

195
    def build_status_object(status, message = '')
196
      Gitlab::GitAccessStatus.new(status, message)
197
    end
198 199
  end
end