summaryrefslogtreecommitdiff
path: root/lib/gitlab/backend/grack_auth.rb
blob: c629144118c2c92e6ce6827fdf481cf7235df6a5 (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
require_relative 'shell_env'
require_relative 'grack_helpers'

module Grack
  class Auth < Rack::Auth::Basic
    include Helpers

    attr_accessor :user, :project, :ref, :env

    def call(env)
      @env = env
      @request = Rack::Request.new(env)
      @auth = Request.new(env)

      # Need this patch due to the rails mount

      # Need this if under RELATIVE_URL_ROOT
      unless Gitlab.config.gitlab.relative_url_root.empty?
        # If website is mounted using relative_url_root need to remove it first
        @env['PATH_INFO'] = @request.path.sub(Gitlab.config.gitlab.relative_url_root,'')
      else
        @env['PATH_INFO'] = @request.path
      end

      @env['SCRIPT_NAME'] = ""

      auth!
    end

    private

    def auth!
      return render_not_found unless project

      if @auth.provided?
        return bad_request unless @auth.basic?

        # Authentication with username and password
        login, password = @auth.credentials

        # Allow authentication for GitLab CI service
        # if valid token passed
        if login == "gitlab-ci-token" && project.gitlab_ci?
          token = project.gitlab_ci_service.token

          if token.present? && token == password && service_name == 'git-upload-pack'
            return @app.call(env)
          end
        end

        @user = authenticate_user(login, password)

        if @user
          Gitlab::ShellEnv.set_env(@user)
          @env['REMOTE_USER'] = @auth.username
        else
          return unauthorized
        end

      else
        return unauthorized unless project.public?
      end

      if authorized_git_request?
        @app.call(env)
      else
        unauthorized
      end
    end

    def authorized_git_request?
      authorize_request(service_name)
    end

    def authenticate_user(login, password)
      auth = Gitlab::Auth.new
      auth.find(login, password)
    end

    def authorize_request(service)
      case service
      when 'git-upload-pack'
        can?(user, :download_code, project)
      when'git-receive-pack'
        refs.each do |ref|
          action = if project.protected_branch?(ref)
                     :push_code_to_protected_branches
                   else
                     :push_code
                   end

          return false unless can?(user, action, project)
        end

        true
      else
        false
      end
    end

    def service_name
      if @request.get?
        @request.params['service']
      elsif @request.post?
        File.basename(@request.path)
      else
        nil
      end
    end

    def project
      @project ||= project_by_path(@request.path_info)
    end

    def refs
      @refs ||= parse_refs
    end

    def parse_refs
      input = if @env["HTTP_CONTENT_ENCODING"] =~ /gzip/
                Zlib::GzipReader.new(@request.body).read
              else
                @request.body.read
              end

      # Need to reset seek point
      @request.body.rewind

      # Parse refs
      refs = input.force_encoding('ascii-8bit').scan(/refs\/heads\/([\/\w\.-]+)/n).flatten.compact

      # Cleanup grabare from refs
      # if push to multiple branches
      refs.map do |ref|
        ref.gsub(/00.*/, "")
      end
    end
  end
end