uploads_actions_shared_examples.rb 11.5 KB
Newer Older
1
2
# frozen_string_literal: true

Jarka Kadlecova's avatar
Jarka Kadlecova committed
3
4
shared_examples 'handle uploads' do
  let(:user)  { create(:user) }
5
6
  let(:jpg)   { fixture_file_upload('spec/fixtures/rails_sample.jpg', 'image/jpg') }
  let(:txt)   { fixture_file_upload('spec/fixtures/doc_sample.txt', 'text/plain') }
7
8
  let(:secret) { FileUploader.generate_secret }
  let(:uploader_class) { FileUploader }
Jarka Kadlecova's avatar
Jarka Kadlecova committed
9

10
11
  it_behaves_like 'handle uploads authorize'

Jarka Kadlecova's avatar
Jarka Kadlecova committed
12
13
14
  describe "POST #create" do
    context 'when a user is not authorized to upload a file' do
      it 'returns 404 status' do
15
        post :create, params: params.merge(file: jpg), format: :json
Jarka Kadlecova's avatar
Jarka Kadlecova committed
16
17
18
19
20
21
22
23
24
25
26
27
        expect(response.status).to eq(404)
      end
    end

    context 'when a user can upload a file' do
      before do
        sign_in(user)
        model.add_developer(user)
      end

      context "without params['file']" do
        it "returns an error" do
28
          post :create, params: params, format: :json
Jarka Kadlecova's avatar
Jarka Kadlecova committed
29
30
31
32
33
34
35

          expect(response).to have_gitlab_http_status(422)
        end
      end

      context 'with valid image' do
        before do
36
          post :create, params: params.merge(file: jpg), format: :json
Jarka Kadlecova's avatar
Jarka Kadlecova committed
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
        end

        it 'returns a content with original filename, new link, and correct type.' do
          expect(response.body).to match '\"alt\":\"rails_sample\"'
          expect(response.body).to match "\"url\":\"/uploads"
        end

        # NOTE: This is as close as we're getting to an Integration test for this
        # behavior. We're avoiding a proper Feature test because those should be
        # testing things entirely user-facing, which the Upload model is very much
        # not.
        it 'creates a corresponding Upload record' do
          upload = Upload.last

          aggregate_failures do
            expect(upload).to exist
            expect(upload.model).to eq(model)
          end
        end
      end

      context 'with valid non-image file' do
        before do
60
          post :create, params: params.merge(file: txt), format: :json
Jarka Kadlecova's avatar
Jarka Kadlecova committed
61
62
63
64
65
66
67
68
69
70
71
        end

        it 'returns a content with original filename, new link, and correct type.' do
          expect(response.body).to match '\"alt\":\"doc_sample.txt\"'
          expect(response.body).to match "\"url\":\"/uploads"
        end
      end
    end
  end

  describe "GET #show" do
72
73
74
75
76
77
    let(:filename) { "rails_sample.jpg" }

    let(:upload_service) do
      UploadService.new(model, jpg, uploader_class).execute
    end

Jarka Kadlecova's avatar
Jarka Kadlecova committed
78
    let(:show_upload) do
79
      get :show, params: params.merge(secret: secret, filename: filename)
80
81
82
    end

    before do
83
      allow(FileUploader).to receive(:generate_secret).and_return(secret)
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
      upload_service
    end

    context 'when the secret is invalid' do
      let(:secret) { "../../../../../../../../" }
      let(:filename) { "Gemfile.lock" }
      let(:upload_service) { nil }

      it 'responds with status 404' do
        show_upload

        expect(response).to have_gitlab_http_status(:not_found)
      end

      it 'is a working exploit without the validation' do
        allow_any_instance_of(FileUploader).to receive(:secret) { secret }

        show_upload

        expect(response).to have_gitlab_http_status(:ok)
      end
Jarka Kadlecova's avatar
Jarka Kadlecova committed
105
106
    end

107
108
109
110
111
112
113
114
115
116
    context 'when accessing a specific upload via different model' do
      it 'responds with status 404' do
        params.merge!(other_params)

        show_upload

        expect(response).to have_gitlab_http_status(404)
      end
    end

117
118
119
120
121
122
123
124
125
126
127
128
    context 'when the upload does not have a MIME type that Rails knows' do
      let(:po) { fixture_file_upload('spec/fixtures/missing_metadata.po', 'text/plain') }

      it 'falls back to the null type' do
        UploadService.new(model, po, uploader_class).execute

        get :show, params: params.merge(secret: secret, filename: 'missing_metadata.po')

        expect(response.headers['Content-Type']).to eq('application/octet-stream')
      end
    end

Jarka Kadlecova's avatar
Jarka Kadlecova committed
129
130
131
132
133
134
135
136
137
138
139
140
141
142
    context "when the model is public" do
      before do
        model.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PUBLIC)
      end

      context "when not signed in" do
        context "when the file exists" do
          it "responds with status 200" do
            show_upload

            expect(response).to have_gitlab_http_status(200)
          end
        end

143
144
        context "when neither the uploader nor the model exists" do
          before do
145
            allow_any_instance_of(Upload).to receive(:retrieve_uploader).and_return(nil)
146
147
148
149
150
151
152
153
154
155
            allow(controller).to receive(:find_model).and_return(nil)
          end

          it "responds with status 404" do
            show_upload

            expect(response).to have_gitlab_http_status(404)
          end
        end

Jarka Kadlecova's avatar
Jarka Kadlecova committed
156
        context "when the file doesn't exist" do
157
158
159
160
          before do
            allow_any_instance_of(FileUploader).to receive(:exists?).and_return(false)
          end

Jarka Kadlecova's avatar
Jarka Kadlecova committed
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
          it "responds with status 404" do
            show_upload

            expect(response).to have_gitlab_http_status(404)
          end
        end
      end

      context "when signed in" do
        before do
          sign_in(user)
        end

        context "when the file exists" do
          it "responds with status 200" do
            show_upload

            expect(response).to have_gitlab_http_status(200)
          end
        end

        context "when the file doesn't exist" do
183
184
185
186
          before do
            allow_any_instance_of(FileUploader).to receive(:exists?).and_return(false)
          end

Jarka Kadlecova's avatar
Jarka Kadlecova committed
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
          it "responds with status 404" do
            show_upload

            expect(response).to have_gitlab_http_status(404)
          end
        end
      end
    end

    context "when the model is private" do
      before do
        model.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PRIVATE)
      end

      context "when not signed in" do
        context "when the file exists" do
          context "when the file is an image" do
            before do
              allow_any_instance_of(FileUploader).to receive(:image?).and_return(true)
            end

            it "responds with status 200" do
              show_upload

              expect(response).to have_gitlab_http_status(200)
            end
          end

          context "when the file is not an image" do
216
217
218
219
            before do
              allow_any_instance_of(FileUploader).to receive(:image?).and_return(false)
            end

Jarka Kadlecova's avatar
Jarka Kadlecova committed
220
221
222
223
224
225
226
227
228
            it "redirects to the sign in page" do
              show_upload

              expect(response).to redirect_to(new_user_session_path)
            end
          end
        end

        context "when the file doesn't exist" do
229
230
231
232
          before do
            allow_any_instance_of(FileUploader).to receive(:exists?).and_return(false)
          end

Jarka Kadlecova's avatar
Jarka Kadlecova committed
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
          it "redirects to the sign in page" do
            show_upload

            expect(response).to redirect_to(new_user_session_path)
          end
        end
      end

      context "when signed in" do
        before do
          sign_in(user)
        end

        context "when the user has access to the project" do
          before do
            model.add_developer(user)
          end

          context "when the file exists" do
            it "responds with status 200" do
              show_upload

              expect(response).to have_gitlab_http_status(200)
            end
          end

          context "when the file doesn't exist" do
260
261
262
263
            before do
              allow_any_instance_of(FileUploader).to receive(:exists?).and_return(false)
            end

Jarka Kadlecova's avatar
Jarka Kadlecova committed
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
            it "responds with status 404" do
              show_upload

              expect(response).to have_gitlab_http_status(404)
            end
          end
        end

        context "when the user doesn't have access to the model" do
          context "when the file exists" do
            context "when the file is an image" do
              before do
                allow_any_instance_of(FileUploader).to receive(:image?).and_return(true)
              end

              it "responds with status 200" do
                show_upload

                expect(response).to have_gitlab_http_status(200)
              end
            end

            context "when the file is not an image" do
287
288
289
290
              before do
                allow_any_instance_of(FileUploader).to receive(:image?).and_return(false)
              end

Jarka Kadlecova's avatar
Jarka Kadlecova committed
291
292
293
294
295
296
297
298
299
              it "responds with status 404" do
                show_upload

                expect(response).to have_gitlab_http_status(404)
              end
            end
          end

          context "when the file doesn't exist" do
300
301
302
303
            before do
              allow_any_instance_of(FileUploader).to receive(:exists?).and_return(false)
            end

Jarka Kadlecova's avatar
Jarka Kadlecova committed
304
305
306
307
308
309
310
311
312
313
            it "responds with status 404" do
              show_upload

              expect(response).to have_gitlab_http_status(404)
            end
          end
        end
      end
    end
  end
314
end
315

316
shared_examples 'handle uploads authorize' do
317
318
319
320
321
322
323
324
325
326
327
328
  describe "POST #authorize" do
    context 'when a user is not authorized to upload a file' do
      it 'returns 404 status' do
        post_authorize

        expect(response.status).to eq(404)
      end
    end

    context 'when a user can upload a file' do
      before do
        sign_in(user)
329
330
331
332
333
334

        if model.is_a?(PersonalSnippet)
          model.update!(author: user)
        else
          model.add_developer(user)
        end
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
      end

      context 'and the request bypassed workhorse' do
        it 'raises an exception' do
          expect { post_authorize(verified: false) }.to raise_error JWT::DecodeError
        end
      end

      context 'and request is sent by gitlab-workhorse to authorize the request' do
        shared_examples 'a valid response' do
          before do
            post_authorize
          end

          it 'responds with status 200' do
            expect(response).to have_gitlab_http_status(200)
          end

          it 'uses the gitlab-workhorse content type' do
            expect(response.headers["Content-Type"]).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
          end
        end

        shared_examples 'a local file' do
          it_behaves_like 'a valid response' do
            it 'responds with status 200, location of uploads store and object details' do
              expect(json_response['TempPath']).to eq(uploader_class.workhorse_local_upload_path)
              expect(json_response['RemoteObject']).to be_nil
            end
          end
        end

        context 'when using local storage' do
          it_behaves_like 'a local file'
        end

        context 'when using remote storage' do
          context 'when direct upload is enabled' do
            before do
              stub_uploads_object_storage(uploader_class, direct_upload: true)
            end

            it_behaves_like 'a valid response' do
              it 'responds with status 200, location of uploads remote store and object details' do
379
                expect(json_response).not_to have_key('TempPath')
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
                expect(json_response['RemoteObject']).to have_key('ID')
                expect(json_response['RemoteObject']).to have_key('GetURL')
                expect(json_response['RemoteObject']).to have_key('StoreURL')
                expect(json_response['RemoteObject']).to have_key('DeleteURL')
                expect(json_response['RemoteObject']).to have_key('MultipartUpload')
              end
            end
          end

          context 'when direct upload is disabled' do
            before do
              stub_uploads_object_storage(uploader_class, direct_upload: false)
            end

            it_behaves_like 'a local file'
          end
        end
      end
    end
  end
Jarka Kadlecova's avatar
Jarka Kadlecova committed
400
end