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
      deploy_key: 'This deploy key does not have write access to this project.',
11 12 13
      no_repo: 'A repository for this project does not exist yet.'
    }

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

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

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

J
Jacob Vosmaer 已提交
28
    def check(cmd, changes)
29 30 31 32
      check_protocol!
      check_active_user!
      check_project_accessibility!
      check_command_existence!(cmd)
D
Douwe Maan 已提交
33

34 35
      case cmd
      when *DOWNLOAD_COMMANDS
36
        download_access_check
37
      when *PUSH_COMMANDS
38
        push_access_check(changes)
39
      end
40 41 42 43

      build_status_object(true)
    rescue UnauthorizedError => ex
      build_status_object(false, ex.message)
44 45
    end

46 47 48
    def download_access_check
      if user
        user_download_access_check
49
      elsif deploy_key.nil? && !Guest.can?(:download_code, project)
50
        raise UnauthorizedError, ERROR_MESSAGES[:download]
51 52 53
      end
    end

54
    def push_access_check(changes)
55
      if deploy_key
56
        deploy_key_push_access_check(changes)
57 58
      elsif user
        user_push_access_check(changes)
59
      else
60
        raise UnauthorizedError, ERROR_MESSAGES[:upload]
61 62 63 64
      end
    end

    def user_download_access_check
65
      unless user_can_download_code? || build_can_download_code?
66
        raise UnauthorizedError, ERROR_MESSAGES[:download]
67
      end
68
    end
69

70
    def user_can_download_code?
71
      authentication_abilities.include?(:download_code) && user_access.can_do_action?(:download_code)
72 73
    end

74
    def build_can_download_code?
75
      authentication_abilities.include?(:build_download_code) && user_access.can_do_action?(:build_download_code)
76 77 78
    end

    def user_push_access_check(changes)
79
      unless authentication_abilities.include?(:push_code)
80
        raise UnauthorizedError, ERROR_MESSAGES[:upload]
81 82
      end

83
      if changes.blank?
84
        return # Allow access.
85 86
      end

87 88
      check_repository_existence!
      check_change_access!(changes)
89 90
    end

91
    def deploy_key_push_access_check(changes)
92
      if deploy_key.can_push_to?(project)
93
        check_repository_existence!
94
        check_change_access!(changes) if user
95
      else
96
        raise UnauthorizedError, ERROR_MESSAGES[:deploy_key]
97 98 99
      end
    end

100 101 102 103
    def protocol_allowed?
      Gitlab::ProtocolAccess.allowed?(protocol)
    end

104 105
    private

106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
    def check_protocol!
      unless protocol_allowed?
        raise UnauthorizedError, "Git access over #{protocol.upcase} is not allowed"
      end
    end

    def check_active_user!
      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

130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
    def check_repository_existence!
      unless project.repository.exists?
        raise UnauthorizedError, ERROR_MESSAGES[:no_repo]
      end
    end

    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|
        status = Checks::ChangeAccess.new(change,
                                          user_access: user_access,
                                          project: project).exec
        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

151 152
    def matching_merge_request?(newrev, branch_name)
      Checks::MatchingMergeRequest.new(newrev, branch_name, project).match?
153 154
    end

155 156
    def deploy_key
      actor if actor.is_a?(DeployKey)
157
    end
158

159
    def can_read_project?
160 161 162
      if deploy_key
        project.public? || deploy_key.projects.include?(project)
      elsif user
163 164 165 166 167 168
        user_access.can_read_project?
      else
        Guest.can?(:read_project, project)
      end
    end

169 170
    protected

171 172 173 174 175 176 177 178 179 180 181 182
    def user
      return @user if defined?(@user)

      @user =
        case actor
        when User
          actor
        when Key
          actor.user
        end
    end

183
    def build_status_object(status, message = '')
184
      Gitlab::GitAccessStatus.new(status, message)
185
    end
186 187
  end
end