File: dependency_proxy_for_containers_spec.rb

package info (click to toggle)
gitlab 17.6.5-19
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 629,368 kB
  • sloc: ruby: 1,915,304; javascript: 557,307; sql: 60,639; xml: 6,509; sh: 4,567; makefile: 1,239; python: 406
file content (146 lines) | stat: -rw-r--r-- 5,053 bytes parent folder | download
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
# frozen_string_literal: true

require 'spec_helper'

RSpec.describe 'Group Dependency Proxy for containers', :js, feature_category: :virtual_registry do
  include DependencyProxyHelpers

  include_context 'file upload requests helpers'
  include_context 'with a server running the dependency proxy'

  let_it_be(:user) { create(:user) }
  let_it_be(:group) { create(:group) }
  let_it_be(:sha) { 'a3ed95caeb02ffe68cdd9fd84406680ae93d633cb16422d00e8a7c22955b46d4' }
  let_it_be(:content) { fixture_file_upload("spec/fixtures/dependency_proxy/#{sha}.gz").read }

  let(:image) { 'alpine' }
  let(:url) { capybara_url("/v2/#{group.full_path}/dependency_proxy/containers/#{image}/blobs/sha256:#{sha}") }
  let(:token) { 'token' }
  let(:headers) { { 'Authorization' => "Bearer #{build_jwt(user).encoded}" } }

  subject(:response) do
    HTTParty.get(url, headers: headers)
  end

  let_it_be(:external_server) do
    handler = ->(env) do
      if env['REQUEST_PATH'] == '/token'
        [200, {}, [{ token: 'token' }.to_json]]
      else
        [200, {}, [content]]
      end
    end

    run_server(handler)
  end

  before do
    stub_application_setting(allow_local_requests_from_web_hooks_and_services: true)
    stub_config(dependency_proxy: { enabled: true })
    group.add_developer(user)

    stub_const("DependencyProxy::Registry::AUTH_URL", external_server.base_url)
    stub_const("DependencyProxy::Registry::LIBRARY_URL", external_server.base_url)
  end

  shared_examples 'responds with the file' do
    it 'sends file' do
      expect(subject.code).to eq(200)
      expect(subject.body).to eq(content)
      expect(subject.headers.to_h).to include(
        "content-type" => ["application/gzip"],
        "content-disposition" => ["attachment; filename=\"#{sha}.gz\"; filename*=UTF-8''#{sha}.gz"],
        "content-length" => ["32"]
      )
    end
  end

  shared_examples 'caches the file' do
    it 'caches the file' do
      expect { subject }.to change {
        group.dependency_proxy_blobs.count
      }.from(0).to(1)

      expect(subject.code).to eq(200)
      expect(group.dependency_proxy_blobs.first.file.read).to eq(content)
    end
  end

  shared_examples 'returns not found' do
    it 'returns not found' do
      expect(subject.code).to eq(404)
    end
  end

  context 'fetching a blob' do
    context 'when the blob is cached for the group' do
      let!(:dependency_proxy_blob) { create(:dependency_proxy_blob, group: group) }

      # When authenticating with a job token, the encoded token is the same as
      # that built when authenticating with a user
      context 'with a user or a job token' do
        let_it_be(:headers) { { 'Authorization' => "Bearer #{build_jwt(user).encoded}" } }

        it_behaves_like 'responds with the file'
      end

      context 'with a personal access token' do
        let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
        let_it_be(:headers) { { 'Authorization' => "Bearer #{build_jwt(personal_access_token).encoded}" } }

        it_behaves_like 'responds with the file'
      end

      context 'with a group access token' do
        context 'when a member of the group' do
          let_it_be(:group_bot_user) { create(:user, :project_bot, guest_of: group) }
          let_it_be(:group_access_token) { create(:personal_access_token, user: group_bot_user) }
          let_it_be(:headers) { { 'Authorization' => "Bearer #{build_jwt(group_access_token).encoded}" } }

          it_behaves_like 'responds with the file'
        end

        context 'when not a member of the group' do
          let_it_be(:group_bot_user) { create(:user, :project_bot) }
          let_it_be(:group_access_token) { create(:personal_access_token, user: group_bot_user) }
          let_it_be(:headers) { { 'Authorization' => "Bearer #{build_jwt(group_access_token).encoded}" } }

          it_behaves_like 'returns not found'
        end
      end

      context 'with a group deploy token' do
        before do
          create(:group_deploy_token, group: group, deploy_token: deploy_token)
        end

        context 'with sufficient scopes' do
          let_it_be(:deploy_token) { create(:deploy_token, :group, :dependency_proxy_scopes) }
          let_it_be(:headers) { { 'Authorization' => "Bearer #{build_jwt(deploy_token).encoded}" } }

          it_behaves_like 'responds with the file'
        end

        context 'with insufficient scopes' do
          let_it_be(:deploy_token) { create(:deploy_token, :group) }
          let_it_be(:headers) { { 'Authorization' => "Bearer #{build_jwt(deploy_token).encoded}" } }

          it_behaves_like 'returns not found'
        end
      end
    end
  end

  context 'when the blob must be downloaded' do
    it_behaves_like 'responds with the file'
    it_behaves_like 'caches the file'
  end

  context 'when calling the authentication endpoint' do
    let(:url) { capybara_url('/v2') }

    it 'does not set session cookies' do
      expect(response.headers).not_to include('set-cookie')
    end
  end
end