summaryrefslogtreecommitdiff
path: root/spec/lib/gitlab/middleware/go_spec.rb
blob: 7bac041cd654dee2cd1c0ff65a79a07314adc869 (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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Gitlab::Middleware::Go do
  let(:app) { double(:app) }
  let(:middleware) { described_class.new(app) }
  let(:env) do
    {
      'rack.input' => '',
      'REQUEST_METHOD' => 'GET'
    }
  end

  describe '#call' do
    describe 'when go-get=0' do
      before do
        env['QUERY_STRING'] = 'go-get=0'
      end

      it 'skips go-import generation' do
        expect(app).to receive(:call).with(env).and_return('no-go')
        middleware.call(env)
      end
    end

    describe 'when go-get=1' do
      before do
        env['QUERY_STRING'] = 'go-get=1'
        env['PATH_INFO'] = +"/#{path}"
      end

      shared_examples 'go-get=1' do |enabled_protocol:|
        context 'with simple 2-segment project path' do
          let!(:project) { create(:project, :private, :repository) }

          context 'with subpackages' do
            let(:path) { "#{project.full_path}/subpackage" }

            it 'returns the full project path' do
              expect_response_with_path(go, enabled_protocol, project.full_path, project.default_branch)
            end
          end

          context 'without subpackages' do
            let(:path) { project.full_path }

            it 'returns the full project path' do
              expect_response_with_path(go, enabled_protocol, project.full_path, project.default_branch)
            end
          end
        end

        context 'with a nested project path' do
          let(:group) { create(:group, :nested) }
          let!(:project) { create(:project, :public, :repository, namespace: group) }

          shared_examples 'a nested project' do
            context 'when the project is public' do
              it 'returns the full project path' do
                expect_response_with_path(go, enabled_protocol, project.full_path, project.default_branch)
              end
            end

            context 'when the project is private' do
              before do
                project.update_attribute(:visibility_level, Project::PRIVATE)
              end

              shared_examples 'unauthorized' do
                it 'returns the 2-segment group path' do
                  expect_response_with_path(go, enabled_protocol, group.full_path, project.default_branch)
                end
              end

              context 'when not authenticated' do
                it_behaves_like 'unauthorized'
              end

              context 'when authenticated' do
                let(:current_user) { project.creator }

                before do
                  project.team.add_maintainer(current_user)
                end

                shared_examples 'authenticated' do
                  context 'with access to the project' do
                    it 'returns the full project path' do
                      expect_response_with_path(go, enabled_protocol, project.full_path, project.default_branch)
                    end

                    context 'with an empty ssh_user' do
                      it 'returns the full project path' do
                        allow(Gitlab.config.gitlab_shell).to receive(:ssh_user).and_return('')
                        expect_response_with_path(go, enabled_protocol, project.full_path, project.default_branch)
                      end
                    end
                  end

                  context 'without access to the project' do
                    before do
                      project.team.find_member(current_user).destroy
                    end

                    it_behaves_like 'unauthorized'
                  end

                  context 'with user is blocked' do
                    before do
                      current_user.block
                    end

                    it_behaves_like 'unauthorized'
                  end
                end

                context 'using basic auth' do
                  context 'using a personal access token' do
                    let(:personal_access_token) { create(:personal_access_token, user: current_user) }

                    before do
                      env['REMOTE_ADDR'] = "192.168.0.1"
                      env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(current_user.username, personal_access_token.token)
                    end

                    context 'with api scope' do
                      it_behaves_like 'authenticated'
                    end

                    context 'with read_user scope' do
                      before do
                        personal_access_token.update_attribute(:scopes, [:read_user])
                      end

                      it_behaves_like 'unauthorized'
                    end

                    context 'with a blacklisted ip' do
                      it 'returns forbidden' do
                        expect(Gitlab::Auth).to receive(:find_for_git_client).and_raise(Gitlab::Auth::IpBlacklisted)
                        response = go

                        expect(response[0]).to eq(403)
                        expect(response[1]['Content-Length']).to be_nil
                        expect(response[2]).to eq([''])
                      end
                    end
                  end
                end
              end
            end
          end

          context 'with subpackages' do
            let(:path) { "#{project.full_path}/subpackage" }

            it_behaves_like 'a nested project'
          end

          context 'with a subpackage that is not a valid project path' do
            let(:path) { "#{project.full_path}/---subpackage" }

            it_behaves_like 'a nested project'
          end

          context 'without subpackages' do
            let(:path) { project.full_path }

            it_behaves_like 'a nested project'
          end
        end

        context 'with a bogus path' do
          let(:path) { "http:;url=http://www.example.com'http-equiv='refresh'x='?go-get=1" }

          it 'skips go-import generation' do
            expect(app).to receive(:call).and_return('no-go')

            go
          end
        end

        context 'with a public project without a repository' do
          let!(:project) { create(:project, :public) }
          let(:path) { project.full_path }

          it 'returns 404' do
            response = go

            expect(response[0]).to eq(404)
            expect(response[1]['Content-Type']).to eq('text/html')
            expected_body = %{<html><body>go get #{Gitlab.config.gitlab.url}/#{project.full_path}</body></html>}
            expect(response[2]).to eq([expected_body])
          end
        end

        context 'with a non-standard head' do
          let(:user) { create(:user) }
          let!(:project) { create(:project, :public, :repository) }
          let(:path) { project.full_path }
          let(:default_branch) { 'default_branch' }

          before do
            project.add_maintainer(user)
            project.repository.add_branch(user, default_branch, 'master')
            project.change_head(default_branch)
          end

          it 'returns the full project path' do
            expect_response_with_path(go, enabled_protocol, project.full_path, default_branch)
          end
        end
      end

      context 'with SSH disabled' do
        before do
          stub_application_setting(enabled_git_access_protocol: 'http')
        end

        include_examples 'go-get=1', enabled_protocol: :http
      end

      context 'with HTTP disabled' do
        before do
          stub_application_setting(enabled_git_access_protocol: 'ssh')
        end

        include_examples 'go-get=1', enabled_protocol: :ssh
      end

      context 'with nothing disabled' do
        before do
          stub_application_setting(enabled_git_access_protocol: nil)
        end

        include_examples 'go-get=1', enabled_protocol: nil
      end

      context 'with nothing disabled (blank string)' do
        before do
          stub_application_setting(enabled_git_access_protocol: '')
        end

        include_examples 'go-get=1', enabled_protocol: nil
      end
    end

    def go
      middleware.call(env)
    end

    def expect_response_with_path(response, protocol, path, branch)
      repository_url = case protocol
                       when :ssh
                         shell = Gitlab.config.gitlab_shell
                         user = "#{shell.ssh_user}@" unless shell.ssh_user.empty?
                         "ssh://#{user}#{shell.ssh_host}/#{path}.git"
                       when :http, nil
                         "http://#{Gitlab.config.gitlab.host}/#{path}.git"
                       end
      project_url = "http://#{Gitlab.config.gitlab.host}/#{path}"
      expect(response[0]).to eq(200)
      expect(response[1]['Content-Type']).to eq('text/html')
      expected_body = %{<html><head><meta name="go-import" content="#{Gitlab.config.gitlab.host}/#{path} git #{repository_url}" /><meta name="go-source" content="#{Gitlab.config.gitlab.host}/#{path} #{project_url} #{project_url}/-/tree/#{branch}{/dir} #{project_url}/-/blob/#{branch}{/dir}/{file}#L{line}" /></head><body>go get #{Gitlab.config.gitlab.url}/#{path}</body></html>}
      expect(response[2]).to eq([expected_body])
    end
  end
end