summaryrefslogtreecommitdiff
path: root/lib/gitlab/git_access.rb
blob: 6444cec7eb5d204bee6e407ffba01009b996fe31 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
module Gitlab
  class GitAccess
    DOWNLOAD_COMMANDS = %w{ git-upload-pack git-upload-archive }
    PUSH_COMMANDS = %w{ git-receive-pack }

    attr_reader :params, :project, :git_cmd, :user

    def self.can_push_to_branch?(user, project, ref)
      if project.protected_branch?(ref)  &&
          !(project.developers_can_push_to_protected_branch?(ref) && project.team.developer?(user))
        user.can?(:push_code_to_protected_branches, project)
      else
        user.can?(:push_code, project)
      end
    end

    def check(actor, cmd, project, changes = nil)
      case cmd
      when *DOWNLOAD_COMMANDS
        download_access_check(actor, project)
      when *PUSH_COMMANDS
        if actor.is_a? User
          push_access_check(actor, project, changes)
        elsif actor.is_a? DeployKey
          return build_status_object(false, "Deploy key not allowed to push")
        elsif actor.is_a? Key
          push_access_check(actor.user, project, changes)
        else
          raise 'Wrong actor'
        end
      else
        return build_status_object(false, "Wrong command")
      end
    end

    def download_access_check(actor, project)
      if actor.is_a?(User)
        user_download_access_check(actor, project)
      elsif actor.is_a?(DeployKey)
        if actor.projects.include?(project)
          build_status_object(true)
        else
          build_status_object(false, "Deploy key not allowed to access this project")
        end
      elsif actor.is_a? Key
        user_download_access_check(actor.user, project)
      else
        raise 'Wrong actor'
      end
    end

    def user_download_access_check(user, project)
      if user && user_allowed?(user) && user.can?(:download_code, project)
        build_status_object(true)
      else
        build_status_object(false, "You don't have access")
      end
    end

    def push_access_check(user, project, changes)
      unless user && user_allowed?(user)
        return build_status_object(false, "You don't have access")
      end

      if changes.blank?
        return build_status_object(true)
      end

      unless project.repository.exists?
        return build_status_object(false, "Repository does not exist")
      end

      changes = changes.lines if changes.kind_of?(String)

      # Iterate over all changes to find if user allowed all of them to be applied
      changes.map(&:strip).reject(&:blank?).each do |change|
        status = change_access_check(user, project, change)
        unless status.allowed?
          # If user does not have access to make at least one change - cancel all push
          return status
        end
      end

      return build_status_object(true)
    end

    def change_access_check(user, project, change)
      oldrev, newrev, ref = change.split(' ')

      action = if project.protected_branch?(branch_name(ref))
                 protected_branch_action(project, oldrev, newrev, branch_name(ref))
               elsif protected_tag?(project, tag_name(ref))
                 # Prevent any changes to existing git tag unless user has permissions
                 :admin_project
               else
                 :push_code
               end

      if user.can?(action, project)
        build_status_object(true)
      else
        build_status_object(false, "You don't have permission")
      end
    end

    def forced_push?(project, oldrev, newrev)
      Gitlab::ForcePushCheck.force_push?(project, oldrev, newrev)
    end

    private

    def protected_branch_action(project, oldrev, newrev, branch_name)
      # we dont allow force push to protected branch
      if forced_push?(project, oldrev, newrev)
        :force_push_code_to_protected_branches
      elsif newrev == Gitlab::Git::BLANK_SHA
        # and we dont allow remove of protected branch
        :remove_protected_branches
      elsif project.developers_can_push_to_protected_branch?(branch_name)
        :push_code
      else
        :push_code_to_protected_branches
      end
    end

    def protected_tag?(project, tag_name)
      project.repository.tag_names.include?(tag_name)
    end

    def user_allowed?(user)
      Gitlab::UserAccess.allowed?(user)
    end

    def branch_name(ref)
      ref = ref.to_s
      if ref.start_with?('refs/heads')
        ref.sub(%r{\Arefs/heads/}, '')
      else
        nil
      end
    end

    def tag_name(ref)
      ref = ref.to_s
      if ref.start_with?('refs/tags')
        ref.sub(%r{\Arefs/tags/}, '')
      else
        nil
      end
    end

    protected

    def build_status_object(status, message = '')
      GitAccessStatus.new(status, message)
    end
  end
end