diff --git a/.github/workflows/lint-unit.yml b/.github/workflows/lint-unit.yml
index f9d1eaebc..e97009ec4 100644
--- a/.github/workflows/lint-unit.yml
+++ b/.github/workflows/lint-unit.yml
@@ -82,7 +82,12 @@ jobs:
- name: Run core tests
run: |
bundle exec rake test:core
-
+
+ - name: Run core tests with sqlite
+ run: |
+ sed -i 's/adapter: mysql2/adapter: sqlite3/' config/rmt.yml
+ bundle exec rake test:core
+
- name: Run PubCloud engines tests
run: |
bundle exec rake test:engines
diff --git a/app/controllers/api/connect/base_controller.rb b/app/controllers/api/connect/base_controller.rb
index 1fbeaff07..84de46574 100644
--- a/app/controllers/api/connect/base_controller.rb
+++ b/app/controllers/api/connect/base_controller.rb
@@ -44,4 +44,14 @@ def authenticate_with_token
end
end
+ def system_token_header
+ headers[SYSTEM_TOKEN_HEADER] = @system.system_token
+ end
+
+ def refresh_system_token
+ if system_tokens_enabled?
+ @system.update(system_token: SecureRandom.uuid)
+ system_token_header
+ end
+ end
end
diff --git a/app/controllers/api/connect/v3/systems/products_controller.rb b/app/controllers/api/connect/v3/systems/products_controller.rb
index 2545a9e92..648443ecd 100644
--- a/app/controllers/api/connect/v3/systems/products_controller.rb
+++ b/app/controllers/api/connect/v3/systems/products_controller.rb
@@ -5,6 +5,7 @@ class Api::Connect::V3::Systems::ProductsController < Api::Connect::BaseControll
before_action :check_product_service_and_repositories, only: %i[show activate]
before_action :load_subscription, only: %i[activate upgrade]
before_action :check_base_product_dependencies, only: %i[activate upgrade show]
+ after_action :refresh_system_token, only: %i[activate upgrade], if: -> { request.headers.key?(SYSTEM_TOKEN_HEADER) }
def activate
create_product_activation
@@ -12,7 +13,13 @@ def activate
end
def show
- if @system.products.include? @product
+ if @product.identifier.casecmp?('sles')
+ # if system has SLE Micro
+ # it should access to SLES products
+ sle_micro = @system.products.any? { |p| p.identifier.downcase.include?('sle-micro') }
+ sle_micro_same_arch = @system.products.pluck(:arch).include?(@product.arch) if sle_micro
+ end
+ if @system.products.include?(@product) || sle_micro_same_arch
respond_with(
@product,
serializer: ::V3::ProductSerializer,
diff --git a/app/controllers/api/connect/v3/systems/systems_controller.rb b/app/controllers/api/connect/v3/systems/systems_controller.rb
index 863557279..d9a17154d 100644
--- a/app/controllers/api/connect/v3/systems/systems_controller.rb
+++ b/app/controllers/api/connect/v3/systems/systems_controller.rb
@@ -1,6 +1,7 @@
class Api::Connect::V3::Systems::SystemsController < Api::Connect::BaseController
before_action :authenticate_system
+ after_action :refresh_system_token, only: [:update], if: -> { request.headers.key?(SYSTEM_TOKEN_HEADER) }
def update
if params[:online_at].present?
diff --git a/app/controllers/api/connect/v4/systems/products_controller.rb b/app/controllers/api/connect/v4/systems/products_controller.rb
index 7237e5a80..d4c87adfc 100644
--- a/app/controllers/api/connect/v4/systems/products_controller.rb
+++ b/app/controllers/api/connect/v4/systems/products_controller.rb
@@ -1,5 +1,6 @@
class Api::Connect::V4::Systems::ProductsController < Api::Connect::V3::Systems::ProductsController
+ after_action :refresh_system_token, only: %i[activate upgrade synchronize destroy], if: -> { request.headers.key?(SYSTEM_TOKEN_HEADER) }
def destroy
if @product.base?
raise ActionController::TranslatedError.new(N_('The product "%s" is a base product and cannot be deactivated'), @product.name)
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 686972895..63458615a 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -21,10 +21,10 @@ def authenticate_system(skip_on_duplicated: false)
update_user_agent
# If SYSTEM_TOKEN_HEADER is present, RMT assumes the client uses a SUSEConnect version
- # that supports this feature. In this case, refresh the token and include it in the response.
+ # that supports this feature.
if system_tokens_enabled? && request.headers.key?(SYSTEM_TOKEN_HEADER)
- @system.update(last_seen_at: Time.zone.now, system_token: SecureRandom.uuid)
- headers[SYSTEM_TOKEN_HEADER] = @system.system_token
+ @system.update(last_seen_at: Time.zone.now)
+ system_token_header
# only update last_seen_at each 3 minutes,
# so that a system that calls SCC every second doesn't write + lock the database row
elsif !@system.last_seen_at || @system.last_seen_at < 3.minutes.ago
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 6a7719aa2..c65c602cd 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -23,14 +23,15 @@ class Repository < ApplicationRecord
class << self
def remove_suse_repos_without_tokens!
- where(auth_token: nil).where('external_url LIKE ?', 'https://updates.suse.com%').delete_all
+ where(auth_token: nil).where("external_url LIKE '%.suse.com%'").where(installer_updates: 0).where.not(scc_id: nil).delete_all
end
# Mangles remote repo URL to make a nicer local path, see specs for examples
def make_local_path(url)
uri = URI(url)
path = uri.path.to_s
- path.gsub!(%r{^/repo}, '') if (uri.hostname == 'updates.suse.com')
+ # drop '/repo' from SLE11 paths, to avoid double /repo/repo in local storage path.
+ path.gsub!(%r{^/repo/\$RCE/}, '/$RCE/')
(path == '') ? '/' : path
end
diff --git a/config/rmt.yml b/config/rmt.yml
index 811e44ff2..6a2df5b5c 100644
--- a/config/rmt.yml
+++ b/config/rmt.yml
@@ -20,7 +20,7 @@ database_test:
database: rmt_test
scc:
- host: https://scc.suse.com/connect
+ host: <%= ENV.fetch('SCC_HOST'){ 'https://scc.suse.com/connect' } %>
username: <%= ENV['SCC_USERNAME'] %>
password: <%= ENV['SCC_PASSWORD'] %>
sync_systems: true
diff --git a/engines/instance_verification/app/controllers/instance_verification/billing_check_controller.rb b/engines/instance_verification/app/controllers/instance_verification/billing_check_controller.rb
index 9d00eee26..0217e54d7 100644
--- a/engines/instance_verification/app/controllers/instance_verification/billing_check_controller.rb
+++ b/engines/instance_verification/app/controllers/instance_verification/billing_check_controller.rb
@@ -5,7 +5,7 @@ def check
# belongs to a PAYG or BYOS instance
verification_provider = InstanceVerification.provider.new(
logger,
- nil,
+ request,
nil,
params[:metadata]
)
diff --git a/engines/instance_verification/lib/instance_verification/providers/example.rb b/engines/instance_verification/lib/instance_verification/providers/example.rb
index b773d7855..e4379c39b 100644
--- a/engines/instance_verification/lib/instance_verification/providers/example.rb
+++ b/engines/instance_verification/lib/instance_verification/providers/example.rb
@@ -27,10 +27,6 @@ def validate_instance_data(_instance_data)
end
def parse_instance_data
- if @instance_data.include? ''
- return { 'instance_data' => 'parsed_instance_data' }
- end
-
if @instance_data.include?('SUSE')
if @instance_data.include?('SAP')
return { 'billingProducts' => nil, 'marketplaceProductCodes' => ['6789_SUSE_SAP'] }
@@ -49,4 +45,14 @@ def payg_billing_code?(iid, identifier)
return true if (identifier.casecmp('sles').zero? && instance_billing_info[:billing_product] == SLES_PRODUCT_IDENTIFIER)
return true if (identifier.casecmp('sles_sap').zero? && SLES4SAP_PRODUCT_IDENTIFIER.include?(instance_billing_info[:marketplace_code]))
end
+
+ def instance_identifier
+ 'foo'
+ end
+
+ def allowed_extension?
+ # method to check if a product (extension) meets the criteria
+ # to be activated on SCC or not, i.e. LTSS in Azure Basic VM
+ true
+ end
end
diff --git a/engines/instance_verification/spec/requests/api/connect/v3/systems/products_controller_spec.rb b/engines/instance_verification/spec/requests/api/connect/v3/systems/products_controller_spec.rb
index 9ccbcb1c2..6b7afad35 100644
--- a/engines/instance_verification/spec/requests/api/connect/v3/systems/products_controller_spec.rb
+++ b/engines/instance_verification/spec/requests/api/connect/v3/systems/products_controller_spec.rb
@@ -38,7 +38,7 @@
it 'class instance verification provider' do
expect(InstanceVerification::Providers::Example).to receive(:new)
- .with(be_a(ActiveSupport::Logger), be_a(ActionDispatch::Request), payload, nil).and_call_original
+ .with(be_a(ActiveSupport::Logger), be_a(ActionDispatch::Request), payload, nil).and_call_original.at_least(:once)
allow(File).to receive(:directory?)
allow(Dir).to receive(:mkdir)
allow(FileUtils).to receive(:touch)
@@ -71,13 +71,18 @@
end
context 'when verification provider returns false' do
+ let(:plugin_double) { instance_double('InstanceVerification::Providers::Example') }
+
before do
stub_request(:post, scc_activate_url)
.to_return(
status: 200,
body: { error: 'Unexpected instance verification error has occurred' }.to_json,
headers: {}
- )
+ )
+ allow(InstanceVerification::Providers::Example).to receive(:new).and_return(plugin_double)
+ allow(plugin_double).to receive(:allowed_extension?).and_return(true)
+ allow(plugin_double).to receive(:instance_valid?).and_return(false)
post url, params: payload, headers: headers
end
@@ -113,7 +118,7 @@
it 'class instance verification provider' do
expect(InstanceVerification::Providers::Example).to receive(:new)
- .with(be_a(ActiveSupport::Logger), be_a(ActionDispatch::Request), payload, nil).and_call_original
+ .with(be_a(ActiveSupport::Logger), be_a(ActionDispatch::Request), payload, nil).and_call_original.at_least(:once)
allow(File).to receive(:directory?)
allow(Dir).to receive(:mkdir)
allow(FileUtils).to receive(:touch)
@@ -141,8 +146,9 @@
context 'when verification provider returns false' do
before do
expect(InstanceVerification::Providers::Example).to receive(:new)
- .with(be_a(ActiveSupport::Logger), be_a(ActionDispatch::Request), payload, instance_data).and_return(plugin_double)
+ .with(be_a(ActiveSupport::Logger), be_a(ActionDispatch::Request), payload, instance_data).and_return(plugin_double).at_least(:once)
expect(plugin_double).to receive(:instance_valid?).and_return(false)
+ allow(plugin_double).to receive(:allowed_extension?).and_return(true)
post url, params: payload, headers: headers
end
@@ -155,8 +161,9 @@
context 'when verification provider raises an unhandled exception' do
before do
expect(InstanceVerification::Providers::Example).to receive(:new)
- .with(be_a(ActiveSupport::Logger), be_a(ActionDispatch::Request), payload, instance_data).and_return(plugin_double)
+ .with(be_a(ActiveSupport::Logger), be_a(ActionDispatch::Request), payload, instance_data).and_return(plugin_double).at_least(:once)
expect(plugin_double).to receive(:instance_valid?).and_raise('Custom plugin error')
+ allow(plugin_double).to receive(:allowed_extension?).and_return(true)
post url, params: payload, headers: headers
end
@@ -171,9 +178,9 @@
before do
expect(InstanceVerification::Providers::Example).to receive(:new)
- .with(be_a(ActiveSupport::Logger), be_a(ActionDispatch::Request), payload, instance_data).and_return(plugin_double)
+ .with(be_a(ActiveSupport::Logger), be_a(ActionDispatch::Request), payload, instance_data).and_return(plugin_double).at_least(:once)
expect(plugin_double).to receive(:instance_valid?).and_raise(InstanceVerification::Exception, 'Custom plugin error')
-
+ allow(plugin_double).to receive(:allowed_extension?).and_return(true)
post url, params: payload, headers: headers
end
@@ -227,9 +234,9 @@
end
before do
- allow(InstanceVerification::Providers::Example).to receive(:new)
- .with(nil, nil, nil, instance_data).and_return(plugin_double)
+ allow(InstanceVerification::Providers::Example).to receive(:new).and_return(plugin_double)
allow(plugin_double).to receive(:parse_instance_data).and_return({ InstanceId: 'foo' })
+ allow(plugin_double).to receive(:allowed_extension?).and_return(true)
FactoryBot.create(:subscription, product_classes: product_classes)
stub_request(:post, scc_activate_url)
@@ -339,9 +346,10 @@
end
before do
- allow(InstanceVerification::Providers::Example).to receive(:new)
- .with(nil, nil, nil, instance_data).and_return(plugin_double)
+ allow(InstanceVerification::Providers::Example).to receive(:new).and_return(plugin_double)
+ allow(plugin_double).to receive(:instance_identifier).and_return('foo')
allow(plugin_double).to receive(:parse_instance_data).and_return({ InstanceId: 'foo' })
+ allow(plugin_double).to receive(:allowed_extension?).and_return(true)
allow(InstanceVerification).to receive(:update_cache).with('127.0.0.1', system.login, product.id)
FactoryBot.create(:subscription, product_classes: product_classes)
@@ -380,8 +388,9 @@
before do
allow(InstanceVerification::Providers::Example).to receive(:new)
- .with(nil, nil, nil, instance_data).and_return(plugin_double)
+ .and_return(plugin_double)
allow(plugin_double).to receive(:parse_instance_data).and_return({ InstanceId: 'foo' })
+ allow(plugin_double).to receive(:allowed_extension?).and_return(true)
allow(InstanceVerification).to receive(:update_cache).with('127.0.0.1', system.login, product.id)
FactoryBot.create(:subscription, product_classes: product_classes)
@@ -397,7 +406,7 @@
.to_return(status: 201, body: scc_response_body, headers: {})
expect(InstanceVerification).not_to receive(:update_cache).with('127.0.0.1', system.login, product.id)
-
+ allow(plugin_double).to receive(:allowed_extension?).and_return(true)
post url, params: payload_no_token, headers: headers
end
@@ -409,12 +418,16 @@
end
context 'when the system is hybrid' do
+ before do
+ allow_any_instance_of(InstanceVerification::Providers::Example).to receive(:allowed_extension?).and_return(true)
+ end
+
context "when system doesn't have hw_info" do
let(:system) { FactoryBot.create(:system, :hybrid) }
it 'class instance verification provider' do
expect(InstanceVerification::Providers::Example).to receive(:new)
- .with(be_a(ActiveSupport::Logger), be_a(ActionDispatch::Request), payload, nil).and_call_original
+ .and_call_original.at_least(:once)
allow(File).to receive(:directory?)
allow(Dir).to receive(:mkdir)
allow(FileUtils).to receive(:touch)
@@ -442,7 +455,8 @@
context 'when verification provider returns false' do
before do
expect(InstanceVerification::Providers::Example).to receive(:new)
- .with(be_a(ActiveSupport::Logger), be_a(ActionDispatch::Request), payload, instance_data).and_return(plugin_double)
+ .with(be_a(ActiveSupport::Logger), be_a(ActionDispatch::Request), payload, instance_data).and_return(plugin_double).at_least(:once)
+ allow(plugin_double).to receive(:allowed_extension?).and_return(true)
expect(plugin_double).to receive(:instance_valid?).and_return(false)
post url, params: payload, headers: headers
end
@@ -456,7 +470,8 @@
context 'when verification provider raises an unhandled exception' do
before do
expect(InstanceVerification::Providers::Example).to receive(:new)
- .with(be_a(ActiveSupport::Logger), be_a(ActionDispatch::Request), payload, instance_data).and_return(plugin_double)
+ .with(be_a(ActiveSupport::Logger), be_a(ActionDispatch::Request), payload, instance_data).and_return(plugin_double).at_least(:once)
+ allow(plugin_double).to receive(:allowed_extension?).and_return(true)
expect(plugin_double).to receive(:instance_valid?).and_raise('Custom plugin error')
post url, params: payload, headers: headers
end
@@ -514,8 +529,9 @@
before do
allow(InstanceVerification::Providers::Example).to receive(:new)
- .with(nil, nil, nil, instance_data).and_return(plugin_double)
+ .and_return(plugin_double)
allow(plugin_double).to receive(:parse_instance_data).and_return({ InstanceId: 'foo' })
+ allow(plugin_double).to receive(:allowed_extension?).and_return(true)
FactoryBot.create(:subscription, product_classes: product_classes)
stub_request(:post, scc_activate_url)
diff --git a/engines/scc_proxy/lib/scc_proxy/engine.rb b/engines/scc_proxy/lib/scc_proxy/engine.rb
index e684dc910..47e6557e8 100644
--- a/engines/scc_proxy/lib/scc_proxy/engine.rb
+++ b/engines/scc_proxy/lib/scc_proxy/engine.rb
@@ -25,12 +25,6 @@
Net::HTTPRetriableError
].freeze
-INSTANCE_ID_KEYS = {
- amazon: 'instanceId',
- google: 'instance_id',
- microsoft: 'vmId'
-}.freeze
-
# rubocop:disable Metrics/ModuleLength
module SccProxy
class << self
@@ -43,7 +37,12 @@ class << self
# rubocop:disable ThreadSafety/InstanceVariableInClassMethod
def headers(auth, params)
@instance_id = if params && params.class != String
- get_instance_id(params)
+ InstanceVerification.provider.new(
+ nil,
+ nil,
+ nil,
+ params['instance_data']
+ ).instance_identifier
else
# if it is not JSON, it is the system_token already
# announce system has metadata
@@ -61,18 +60,6 @@ def headers(auth, params)
end
# rubocop:enable ThreadSafety/InstanceVariableInClassMethod
- def get_instance_id(params)
- verification_provider = InstanceVerification.provider.new(
- nil,
- nil,
- nil,
- params['instance_data']
- )
- instance_id_key = INSTANCE_ID_KEYS[params['hwinfo']['cloud_provider'].downcase.to_sym]
- iid = verification_provider.parse_instance_data
- iid[instance_id_key]
- end
-
def prepare_scc_announce_request(uri_path, auth, params)
scc_request = Net::HTTP::Post.new(uri_path, headers(auth, params))
@@ -307,6 +294,7 @@ def scc_upgrade(auth, product, system_login, mode, logger)
end
end
+ # rubocop:disable Metrics/ClassLength
class Engine < ::Rails::Engine
isolate_namespace SccProxy
config.generators.api_only = true
@@ -372,6 +360,12 @@ def has_no_regcode?(auth_header)
protected
def scc_activate_product
+ product_hash = @product.attributes.symbolize_keys.slice(:identifier, :version, :arch)
+ unless InstanceVerification.provider.new(logger, request, product_hash, @system.instance_data).allowed_extension?
+ error = ActionController::TranslatedError.new(N_('Product not supported for this instance'))
+ error.status = :forbidden
+ raise error
+ end
mode = find_mode
unless mode.nil?
# if system is byos or hybrid and there is a token
@@ -540,5 +534,6 @@ def get_system(systems)
end
end
end
+ # rubocop:enable Metrics/ClassLength
end
# rubocop:enable Metrics/ModuleLength
diff --git a/engines/scc_proxy/spec/requests/api/connect/v3/systems/products_controller_spec.rb b/engines/scc_proxy/spec/requests/api/connect/v3/systems/products_controller_spec.rb
index 0ff8417f3..f023a15b9 100644
--- a/engines/scc_proxy/spec/requests/api/connect/v3/systems/products_controller_spec.rb
+++ b/engines/scc_proxy/spec/requests/api/connect/v3/systems/products_controller_spec.rb
@@ -354,7 +354,7 @@
end
let(:product) do
FactoryBot.create(
- :product, :product_sles, :extension, :with_mirrored_repositories, :with_mirrored_extensions,
+ :product, :product_sles_ltss, :extension, :with_mirrored_repositories, :with_mirrored_extensions,
base_products: [system_payg.products.first]
)
end
@@ -415,7 +415,120 @@
allow(File).to receive(:directory?)
allow(FileUtils).to receive(:mkdir_p)
allow(FileUtils).to receive(:touch)
+ allow(InstanceVerification::Providers::Example).to receive(:new).and_return(plugin_double)
+ allow(plugin_double).to receive(:allowed_extension?).and_return(true)
+ allow(InstanceVerification).to receive(:write_cache_file).twice.with(
+ Rails.application.config.repo_cache_dir, "127.0.0.1-#{system_payg.login}-#{product.id}"
+ )
+ allow(InstanceVerification).to receive(:write_cache_file).twice.with(
+ Rails.application.config.registry_cache_dir, "127.0.0.1-#{system_payg.login}"
+ )
+ allow(plugin_double).to receive(:instance_valid?).and_return(true)
+ end
+
+ context 'when LTSS not allowed' do
+ before do
+ allow(plugin_double).to receive(:allowed_extension?).and_return(false)
+ end
+
+ it 'raises an error' do
+ stub_request(:post, scc_register_system_url)
+ .to_return(status: 403, body: { ok: 'OK' }.to_json, headers: {})
+
+ post url, params: payload, headers: headers
+ data = JSON.parse(response.body)
+ expect(data['error']).to include('Product not supported for this instance')
+ end
+ end
+ end
+ end
+ end
+
+ context 'when system has hw info' do
+ let(:instance_data) { '{"instanceId": "dummy_instance_data"}' }
+ let(:new_system_token) { 'BBBBBBBB-BBBB-4BBB-9BBB-BBBBBBBBBBBB' }
+ let(:serialized_service_json) do
+ V3::ServiceSerializer.new(
+ product.service,
+ base_url: URI::HTTP.build({ scheme: response.request.scheme, host: response.request.host }).to_s
+ ).to_json
+ end
+
+ let(:serialized_service_sap_json) do
+ V3::ServiceSerializer.new(
+ product_sap.service,
+ base_url: URI::HTTP.build({ scheme: response.request.scheme, host: response.request.host }).to_s
+ ).to_json
+ end
+
+ context 'when system is connected to SCC' do
+ let(:system_payg) do
+ FactoryBot.create(:system, :payg, :with_system_information, :with_activated_base_product, instance_data: instance_data,
+ system_token: new_system_token)
+ end
+ let(:product) do
+ FactoryBot.create(
+ :product, :product_sles_ltss, :extension, :with_mirrored_repositories, :with_mirrored_extensions,
+ base_products: [system_payg.products.first]
+ )
+ end
+ let(:subscription_response) do
+ {
+ id: 4206714,
+ regcode: 'bar',
+ name: 'SUSE Employee subscription for SUSE Linux Enterprise Server for SAP Applications',
+ type: 'internal',
+ status: 'ACTIVE',
+ starts_at: '2019-03-20T09:48:52.658Z',
+ expires_at: '2024-03-20T09:48:52.658Z',
+ system_limit: '100',
+ systems_count: '156',
+ virtual_count: nil,
+ product_classes: [
+ 'AiO',
+ '7261',
+ 'SLE-HAE-X86',
+ '7261-BETA',
+ 'SLE-HAE-X86-BETA',
+ 'AiO-BETA',
+ '7261-ALPHA',
+ 'SLE-HAE-X86-ALPHA',
+ 'AiO-ALPHA'
+ ],
+ product_ids: [
+ 1959,
+ 1421
+ ],
+ skus: [],
+ systems: [
+ {
+ id: 3021957,
+ login: 'SCC_foo',
+ password: '5ee7273ac6ac4d7f',
+ last_seen_at: '2019-03-20T14:01:05.424Z'
+ }
+ ]
+ }
+ end
+
+ before do
+ allow(plugin_double).to(
+ receive(:instance_valid?)
+ .and_raise(InstanceVerification::Exception, 'Custom plugin error')
+ )
+ end
+ context 'with a valid registration code' do
+ before do
+ stub_request(:post, scc_activate_url)
+ .to_return(
+ status: 201,
+ body: { id: 'bar' }.to_json,
+ headers: {}
+ )
+ allow(File).to receive(:directory?)
+ allow(FileUtils).to receive(:mkdir_p)
+ allow(FileUtils).to receive(:touch)
allow(InstanceVerification).to receive(:write_cache_file).twice.with(
Rails.application.config.repo_cache_dir, "127.0.0.1-#{system_payg.login}-#{product.id}"
)
diff --git a/engines/strict_authentication/app/controllers/strict_authentication/authentication_controller.rb b/engines/strict_authentication/app/controllers/strict_authentication/authentication_controller.rb
index 311c38fc0..b3fabfab6 100644
--- a/engines/strict_authentication/app/controllers/strict_authentication/authentication_controller.rb
+++ b/engines/strict_authentication/app/controllers/strict_authentication/authentication_controller.rb
@@ -7,27 +7,30 @@ class AuthenticationController < ::ApplicationController
# This is the endpoint for nginx subrequest auth check
def check
request_uri = request.headers['X-Original-URI']
- auth_result = path_allowed?(request.headers['X-Original-URI'])
+ auth_result = path_allowed?(request.headers)
logger.info "Authentication subrequest for #{request_uri} -- #{auth_result ? 'allowed' : 'denied'}"
head auth_result ? :ok : :forbidden
end
protected
- def path_allowed?(path)
+ def path_allowed?(headers)
+ path = headers['X-Original-URI']
return false if path.blank?
+
return true if path =~ %r{/product\.license/}
path = '/' + path.gsub(/^#{RMT::DEFAULT_MIRROR_URL_PREFIX}/, '')
-
# Allow access to SLES 12 and 12-SP1 repos for systems migrating from SLES 11
has_sles11 = @system.products.where(identifier: 'SUSE_SLES').first
return true if (has_sles11 && (path =~ %r{/12/} || path =~ %r{/12-SP1/}))
- all_allowed_paths.find { |allowed_path| path =~ /^#{Regexp.escape(allowed_path)}/ }
+ all_allowed_paths(headers).find { |allowed_path| path =~ /^#{Regexp.escape(allowed_path)}/ }
end
- def all_allowed_paths
+ # rubocop:disable Metrics/CyclomaticComplexity
+ # rubocop:disable Metrics/PerceivedComplexity
+ def all_allowed_paths(headers)
# return all versions of the same product and arch
# (that the system has available with that subscription)
# in order to validate access not only for current product but others
@@ -36,10 +39,31 @@ def all_allowed_paths
# to them or verify paths
all_product_versions = @system.products.map { |p| Product.where(identifier: p.identifier, arch: p.arch) }.flatten
allowed_paths = all_product_versions.map { |prod| prod.repositories.pluck(:local_path) }.flatten
+ # Allow SLE Micro to access all free SLES repositories
+ sle_micro = @system.products.any? { |p| p.identifier.downcase.include?('sle-micro') }
+ if sle_micro
+ system_products_archs = @system.products.pluck(:arch)
+ product_free_sles_modules_only = Product.where(
+ "(lower(identifier) like 'sle-module%' or lower(identifier) like 'packagehub')
+ and lower(identifier) not like '%sap%'
+ and arch = '#{system_products_archs.first}'
+ and free = 1"
+ )
+ end
+ same_arch = product_free_sles_modules_only.any? { |p| system_products_archs.include?(p.arch) } if product_free_sles_modules_only.present?
+ allowed_paths += product_free_sles_modules_only.map { |prod| prod.repositories.pluck(:local_path) }.flatten if same_arch
+
# for the SUMa PAYG offers, RMT access verification code allows access
# to the SUMa Client Tools channels and SUMa Proxy channels
# when product is SUMA_Server and PAYG or SUMA_Server and used as SCC proxy
- manager_prod = @system.products.any? { |p| p.identifier.downcase.include?('manager-server') }
+ manager_prod = @system.products.any? do |p|
+ manager = p.identifier.downcase.include?('manager-server')
+ # SUMA 5.0 must have access to SUMA 4.3, 4.2 and so on
+ micro = p.identifier.downcase.include?('sle-micro')
+ instance_id_header = headers.fetch('X-Instance-Identifier', '').casecmp('suse-manager-server').zero?
+ instance_version_header = headers.fetch('X-Instance-Version', '0').split('.')[0] >= '5'
+ manager || (micro && instance_id_header && instance_version_header)
+ end
if manager_prod
# add all SUMA products paths
@@ -49,5 +73,7 @@ def all_allowed_paths
end
allowed_paths
end
+ # rubocop:enable Metrics/CyclomaticComplexity
+ # rubocop:enable Metrics/PerceivedComplexity
end
end
diff --git a/engines/strict_authentication/spec/requests/strict_authentication/authentication_controller_spec.rb b/engines/strict_authentication/spec/requests/strict_authentication/authentication_controller_spec.rb
index c921d3855..fcb5f4e03 100644
--- a/engines/strict_authentication/spec/requests/strict_authentication/authentication_controller_spec.rb
+++ b/engines/strict_authentication/spec/requests/strict_authentication/authentication_controller_spec.rb
@@ -5,7 +5,7 @@ module StrictAuthentication
RSpec.describe AuthenticationController, type: :request do
subject { response }
- let(:system) { FactoryBot.create(:system, :with_activated_product) }
+ let(:system) { FactoryBot.create(:system, :with_activated_product_sle_micro) }
describe '#check' do
context 'without authentication' do
@@ -39,6 +39,36 @@ module StrictAuthentication
its(:code) { is_expected.to eq '403' }
end
+ context 'when requesting a file in an activated SLES repo on a SLE Micro system' do
+ let(:free_product) do
+ prod = FactoryBot.create(
+ :product, :module, :with_mirrored_repositories
+ )
+ prod.identifier = 'sle-module-foo'
+ prod.arch = system.products.first.arch
+ prod.save!
+ prod
+ end
+ let(:requested_uri) { '/repo' + free_product.repositories.first[:local_path] + '/repodata/repomd.xml' }
+
+ its(:code) { is_expected.to eq '200' }
+ end
+
+ context 'when requesting a file in an activated SLES SAP repo on a SLE Micro system' do
+ let(:free_product) do
+ prod = FactoryBot.create(
+ :product, :module, :with_mirrored_repositories
+ )
+ prod.identifier = 'sle-module-foo-sap'
+ prod.arch = system.products.first.arch
+ prod.save!
+ prod
+ end
+ let(:requested_uri) { '/repo' + free_product.repositories.first[:local_path] + '/repodata/repomd.xml' }
+
+ its(:code) { is_expected.to eq '403' }
+ end
+
context 'when requesting a file in an activated repo' do
let(:requested_uri) { '/repo' + system.repositories.first[:local_path] + '/repodata/repomd.xml' }
diff --git a/package/obs/rmt-server.changes b/package/obs/rmt-server.changes
index 71ad51f29..c84e3dbf6 100644
--- a/package/obs/rmt-server.changes
+++ b/package/obs/rmt-server.changes
@@ -1,3 +1,19 @@
+-------------------------------------------------------------------
+Wed Oct 30 09:01:32 UTC 2024 - Natnael Getahun
+
+- Version 2.20 (unreleased)
+ * RMT packaging: don't overwrite custom sync/mirror timer config on package update
+ * Extend column size for repository and file paths (bsc#1229152)
+ * Forward suseconnect client user-agents to SCC
+ * rmt-server-pubcloud:
+ * Fix LTSS product verification (bsc#1230154)
+ * Fix activations check when no product info is available (bsc#1230157)
+ * Fix Azure SCC connection (bsc#1233314)
+ * Deny access of Azure Basic type images to LTSS
+ * Allow SLE Micro system to access SLES repositories (bsc#1230419)
+ * Skip system token rotation in read-only APIs
+ * Enable RMT to handle the new dl.suse.com CDN domain (bsc#1234641)
+
-------------------------------------------------------------------
Wed Aug 21 15:28:43 UTC 2024 - Jesús Bermúdez Velázquez
diff --git a/spec/factories/products.rb b/spec/factories/products.rb
index 30532e1fc..4612dfb00 100644
--- a/spec/factories/products.rb
+++ b/spec/factories/products.rb
@@ -57,6 +57,23 @@
friendly_version { '15 SP3' }
end
+ trait :product_sles_ltss do
+ identifier { 'SLES-LTSS' }
+ name { 'SUSE Linux Enterprise Server LTSS' }
+ description { 'SUSE Linux Enterprise offers a comprehensive suite of products...' }
+ shortname { 'SLES15-SP3-LTSS' }
+ former_identifier { 'SLES_LTSS' }
+ product_type { 'extension' }
+ product_class { 'LTSS' }
+ release_type { nil }
+ release_stage { 'released' }
+ version { '15.3' }
+ arch { 'x86_64' }
+ free { false }
+ cpe { 'cpe:/o:suse:sles:15:sp3' }
+ friendly_version { '15 SP3' }
+ end
+
trait :product_sles_sap do
identifier { 'SLES_SAP' }
name { 'SUSE Linux Enterprise Server' }
@@ -73,6 +90,22 @@
friendly_version { '15 SP3' }
end
+ trait :product_sle_micro do
+ identifier { 'SLE-Micro' }
+ name { 'SUSE Linux Enterprise Server' }
+ description { 'SUSE Linux Enterprise offers a comprehensive suite of products...' }
+ shortname { 'SLES15-SP6' }
+ former_identifier { 'SUSE_SLES_MICRO' }
+ product_type { :base }
+ release_type { nil }
+ release_stage { 'released' }
+ version { '15.6' }
+ arch { 'x86_64' }
+ free { false }
+ cpe { 'cpe:/o:suse:sles_sap:15:sp6' }
+ friendly_version { '15 SP6' }
+ end
+
trait :extension do
product_type { 'extension' }
end
diff --git a/spec/factories/systems.rb b/spec/factories/systems.rb
index 8fb81aa38..ceab7d02e 100644
--- a/spec/factories/systems.rb
+++ b/spec/factories/systems.rb
@@ -57,6 +57,17 @@
end
end
+ trait :with_activated_product_sle_micro do
+ transient do
+ product { create(:product, :product_sle_micro, :with_mirrored_repositories) }
+ subscription { nil }
+ end
+
+ after :create do |system, evaluator|
+ create(:activation, system: system, service: evaluator.product.service, subscription: evaluator.subscription)
+ end
+ end
+
trait :with_system_information do
system_information do
{
diff --git a/spec/lib/rmt/cli/smt_importer_spec.rb b/spec/lib/rmt/cli/smt_importer_spec.rb
index d77404a52..17063bbc0 100644
--- a/spec/lib/rmt/cli/smt_importer_spec.rb
+++ b/spec/lib/rmt/cli/smt_importer_spec.rb
@@ -1,7 +1,7 @@
require 'rails_helper'
require 'rmt/cli/smt_importer'
-describe RMT::CLI::SMTImporter do
+describe RMT::CLI::SMTImporter, :skip_sqlite do
let(:data_dir) { File.join(Dir.pwd, 'spec/fixtures/files/csv') }
let(:no_systems) { false }
let(:importer) { described_class.new(data_dir, no_systems) }
diff --git a/spec/lib/rmt/lockfile_spec.rb b/spec/lib/rmt/lockfile_spec.rb
index d1b86f968..c06644bc3 100644
--- a/spec/lib/rmt/lockfile_spec.rb
+++ b/spec/lib/rmt/lockfile_spec.rb
@@ -3,7 +3,7 @@
RSpec.describe RMT::Lockfile do
let(:lock_name) { nil }
- describe '#lock' do
+ describe '#lock', :skip_sqlite do
subject(:lock) { described_class.lock(lock_name) { nil } }
context 'with an unnamed lock' do
diff --git a/spec/lib/rmt/scc_spec.rb b/spec/lib/rmt/scc_spec.rb
index 802cf1c8a..f1e3b9d61 100644
--- a/spec/lib/rmt/scc_spec.rb
+++ b/spec/lib/rmt/scc_spec.rb
@@ -238,7 +238,7 @@
described_class.new.sync
end
- it 'removes existing predecessor associations' do
+ it 'removes existing predecessor associations', :skip_sqlite do
expect { ProductPredecessorAssociation.find(existing_association.id) }.to raise_error(ActiveRecord::RecordNotFound)
end
end
@@ -261,7 +261,18 @@
:repository,
:with_products,
auth_token: nil,
- external_url: 'https://example.com/repos/not/updates.suse.com/'
+ external_url: 'https://installer-updates.suse.com/repos/not/updates',
+ installer_updates: true
+ )
+ end
+ let!(:custom_repo) do
+ FactoryBot.create(
+ :repository,
+ :with_products,
+ auth_token: nil,
+ external_url: 'http://customer.com/stuff.suse.com/x86',
+ installer_updates: false,
+ scc_id: nil
)
end
@@ -297,6 +308,10 @@ def scc
it 'other repos without auth_tokens persist' do
expect { other_repo_without_token.reload }.not_to raise_error
end
+
+ it 'custom repos without auth_tokens persist' do
+ expect { custom_repo.reload }.not_to raise_error
+ end
end
describe '#export' do
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 3c958ac15..3c49cd6fa 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -146,7 +146,7 @@
expect(friendly_id).to eq('my-repo-100000')
end
- it 'appends with unicode within the chain' do
+ it 'appends with unicode within the chain', :skip_sqlite do
create(:repository, friendly_id: 'my-repo')
create(:repository, friendly_id: 'my-repö-1')
expect(friendly_id).to eq('my-repo-2')
@@ -165,13 +165,13 @@
expect(friendly_id).to eq('dümmy-repö')
end
- it 'appends with unicode within the chain' do
+ it 'appends with unicode within the chain', :skip_sqlite do
create(:repository, friendly_id: 'dummy-repo')
create(:repository, friendly_id: 'dümmy-repö-1')
expect(friendly_id).to eq('dümmy-repö-2')
end
- it 'correctly appends' do
+ it 'correctly appends', :skip_sqlite do
create(:repository, friendly_id: 'dummy-repo')
create(:repository, friendly_id: 'dummy-repo-1')
expect(friendly_id).to eq('dümmy-repö-2')
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
index 5d71fea9f..9c8bffe57 100644
--- a/spec/rails_helper.rb
+++ b/spec/rails_helper.rb
@@ -58,6 +58,12 @@
config.filter_rails_from_backtrace!
# arbitrary gems may also be filtered via:
# config.filter_gems_from_backtrace("gem name")
+
+ # Skipping some tests when running with (experimental) sqlite backend.
+ # Some tests / code parts are using specific mysql behavior
+ if ActiveRecord::Base.connection.adapter_name == 'SQLite'
+ config.filter_run_excluding skip_sqlite: true
+ end
end
Shoulda::Matchers.configure do |config|
diff --git a/spec/requests/api/connect/base_controller_spec.rb b/spec/requests/api/connect/base_controller_spec.rb
index 70ef9aaff..8832909ea 100644
--- a/spec/requests/api/connect/base_controller_spec.rb
+++ b/spec/requests/api/connect/base_controller_spec.rb
@@ -55,15 +55,6 @@ def require_product
end
end
- shared_examples 'updates the system token' do
- it 'updates the system token' do
- allow(SecureRandom).to receive(:uuid).and_return(new_system_token)
-
- expect { get :service, params: { id: 1 } }
- .to change { system.reload.system_token }
- .from(current_system_token).to(new_system_token)
- end
- end
shared_examples "does not update the old system's token" do
it 'does not update the system token' do
@@ -74,8 +65,6 @@ def require_product
shared_examples 'creates a duplicate system' do
it 'creates a new System (duplicate)' do
- allow(SecureRandom).to receive(:uuid).and_return(new_system_token)
-
expect { get :service, params: { id: 1 } }
.to change { System.get_by_credentials(system.login, system.password).count }
.by(1)
@@ -85,7 +74,6 @@ def require_product
expect(duplicate_system).not_to eq(system)
expect(duplicate_system.activations.count).to eq(system.activations.count)
expect(duplicate_system.system_token).not_to eq(system.system_token)
- expect(duplicate_system.system_token).to eq(new_system_token)
end
end
@@ -182,8 +170,7 @@ def require_product
let(:system) { create(:system, hostname: 'system') }
include_examples 'does not create a duplicate system'
- include_examples 'updates the system token'
- include_examples 'responds with a new token'
+ include_examples "does not update the old system's token"
end
context 'when the system has a token and the header matches it' do
@@ -193,8 +180,7 @@ def require_product
let(:system) { create(:system, hostname: 'system', system_token: current_system_token) }
include_examples 'does not create a duplicate system'
- include_examples 'updates the system token'
- include_examples 'responds with a new token'
+ include_examples "does not update the old system's token"
end
context 'when the system has a token and the header is blank' do
@@ -208,7 +194,6 @@ def require_product
include_examples "does not update the old system's token"
include_examples 'creates a duplicate system'
- include_examples 'responds with a new token'
end
context 'when the system has a token and the header does not match it' do
diff --git a/spec/requests/api/connect/v3/systems/activations_controller_spec.rb b/spec/requests/api/connect/v3/systems/activations_controller_spec.rb
index a4ff0a562..6a62ee2bd 100644
--- a/spec/requests/api/connect/v3/systems/activations_controller_spec.rb
+++ b/spec/requests/api/connect/v3/systems/activations_controller_spec.rb
@@ -69,5 +69,28 @@
expect(system.scc_synced_at).to be_nil
end
end
+
+ context 'system token header' do
+ context 'when system token header is present in request' do
+ let(:token_headers) do
+ authenticated_headers.merge({ 'System-Token' => 'some_token' })
+ end
+
+ it 'sets system token in response headers' do
+ get url, headers: token_headers
+ expect(response.code).to eq '200'
+ expect(response.headers).to include('System-Token')
+ expect(response.headers['System-Token']).not_to be_nil
+ expect(response.headers['System-Token']).not_to be_empty
+ end
+
+ it 'does not set system token header if no system token header in request' do
+ get url, headers: authenticated_headers
+
+ expect(response.code).to eq '200'
+ expect(response.headers).not_to include('System-Token')
+ end
+ end
+ end
end
end
diff --git a/spec/requests/api/connect/v3/systems/products_controller_spec.rb b/spec/requests/api/connect/v3/systems/products_controller_spec.rb
index 60c1bbb0d..9f6c47916 100644
--- a/spec/requests/api/connect/v3/systems/products_controller_spec.rb
+++ b/spec/requests/api/connect/v3/systems/products_controller_spec.rb
@@ -135,6 +135,26 @@
end
end
+ shared_context 'activate with token in request headers' do
+ let(:payload) do
+ {
+ identifier: product.identifier,
+ version: product.version,
+ arch: product.arch,
+ token: regcode
+ }
+ end
+
+ before { post url, headers: { 'System-Token' => 'existing_token' }.merge(headers), params: payload }
+ subject do
+ Struct.new(:body, :code, :headers).new(
+ JSON.parse(response.body, symbolize_names: true),
+ response.status,
+ response.headers
+ )
+ end
+ end
+
context 'unknown subscription' do
include_context 'with subscriptions'
let(:regcode) { 'NOT-EXISTING-SUBSCRIPTION' }
@@ -173,6 +193,17 @@
expect(activation.product).to eq(product)
end
end
+
+ context 'token update after activation is success' do
+ let(:subscription) { create :subscription, :with_products }
+ let(:product) { subscription.products.first }
+ let(:regcode) { subscription.regcode }
+
+ include_context 'activate with token in request headers'
+ its(:code) { is_expected.to eq(201) }
+ its(:headers) { is_expected.to include('System-Token') }
+ its(:headers['System-Token']) { is_expected.not_to eq('existing_token') }
+ end
end
end
@@ -225,6 +256,18 @@
its(:body) { is_expected.to eq(serialized_json) }
end
+ describe 'response header should contain token' do
+ subject { response }
+
+ let(:token_headers) do
+ headers.merge({ 'System-Token' => 'some_token' })
+ end
+
+ before { get url, headers: token_headers, params: payload }
+ its(:code) { is_expected.to eq('200') }
+ its(:headers) { is_expected.to include('System-Token') }
+ end
+
describe 'response with "-" in version' do
subject { response }
@@ -246,6 +289,33 @@
end
end
+ context 'when SLE Micro product is activated' do
+ let(:system) { FactoryBot.create(:system, :with_activated_product_sle_micro) }
+ let(:product) { FactoryBot.create(:product, :product_sles, :with_mirrored_repositories) }
+ let(:payload) do
+ {
+ identifier: product.identifier,
+ version: product.version,
+ arch: system.products.first.arch
+ }
+ end
+ let(:serialized_json) do
+ V3::ProductSerializer.new(
+ product,
+ base_url: URI::HTTP.build({ scheme: response.request.scheme, host: response.request.host }).to_s
+ ).to_json
+ end
+
+ describe 'response' do
+ subject { response }
+
+ before { get url, headers: headers, params: payload }
+
+ its(:code) { is_expected.to eq('200') }
+ its(:body) { is_expected.to eq(serialized_json) }
+ end
+ end
+
context 'with eula_url' do
subject { response }
@@ -312,6 +382,18 @@
system.reload
end
+ it 'calls refresh_system_token after upgrade action when system token header is present' do
+ put url, headers: headers.merge('System-Token' => 'test_token'), params: payload
+ expect(response.code).to eq('201')
+ expect(response.headers).to include('System-Token')
+ expect(response.headers['System-Token']).not_to eq('test_token')
+ end
+
+ it 'No update in token after upgrade action when system token header is absent' do
+ put url, headers: headers, params: payload
+ expect(response.code).to eq('201')
+ expect(response.headers).not_to include('System-Token')
+ end
context 'new product' do
its(:code) { is_expected.to eq('201') }
its(:body) { is_expected.to eq(serialized_json) }
diff --git a/spec/requests/api/connect/v3/systems/systems_controller_spec.rb b/spec/requests/api/connect/v3/systems/systems_controller_spec.rb
index 95eede620..91f73d4f4 100644
--- a/spec/requests/api/connect/v3/systems/systems_controller_spec.rb
+++ b/spec/requests/api/connect/v3/systems/systems_controller_spec.rb
@@ -125,6 +125,25 @@
expect(system.reload.system_information_hash[:user_agent]).to be_nil
end
end
+
+ context 'response header should contain token' do
+ let(:headers) { auth_header.merge('System-Token': 'existing-token') }
+
+ it 'contains refreshed token in response' do
+ update_action
+ expect(response.headers).to include('System-Token')
+ expect(response.headers['System-Token']).not_to equal('existing-token')
+ end
+ end
+
+ context 'response header should not contain token' do
+ let(:headers) { auth_header }
+
+ it 'contains refreshed token in response' do
+ update_action
+ expect(response.headers).not_to include('System-Token')
+ end
+ end
end
describe '#deregister' do
diff --git a/spec/requests/api/connect/v4/systems/products_controller_spec.rb b/spec/requests/api/connect/v4/systems/products_controller_spec.rb
index 8bf9a64f4..6f960a941 100644
--- a/spec/requests/api/connect/v4/systems/products_controller_spec.rb
+++ b/spec/requests/api/connect/v4/systems/products_controller_spec.rb
@@ -140,4 +140,77 @@
end
end
end
+
+ describe 'system token refresh' do
+ let(:system) { FactoryBot.create(:system, :with_activated_base_product) }
+ let(:headers) { auth_header.merge(version_header).merge('System-Token' => 'existing_token') }
+
+ context 'token refresh for destroy action' do
+ let(:product) { FactoryBot.create(:product, :extension, :with_mirrored_repositories, :activated, system: system) }
+ let(:payload) { { identifier: product.identifier, version: product.version, arch: product.arch } }
+
+ it 'refreshes system token when System-Token header is present' do
+ delete connect_systems_products_url,
+ headers: headers,
+ params: payload
+
+ expect(response.status).to eq(200)
+ expect(response.headers).to include('System-Token')
+ expect(response.headers['System-Token']).not_to eq('existing_token')
+ end
+
+ it 'does not refresh token when System-Token header is absent' do
+ headers_without_token = auth_header.merge(version_header)
+ expect_any_instance_of(described_class).not_to receive(:refresh_system_token)
+
+ delete connect_systems_products_url,
+ headers: headers_without_token,
+ params: payload
+
+ expect(response.status).to eq(200)
+ expect(response.headers).not_to include('System-Token')
+ end
+ end
+
+ context 'token refresh for synchronize action' do
+ let(:path) { '/connect/systems/products/synchronize' }
+
+ it 'refreshes system token when System-Token header is present' do
+ params = system.products.map do |product|
+ {
+ identifier: product.identifier,
+ version: product.version,
+ arch: product.arch,
+ release_type: product.release_type
+ }
+ end
+ post path,
+ params: { products: params },
+ headers: headers
+
+ expect(response.status).to eq(200)
+ expect(response.headers).to include('System-Token')
+ expect(response.headers['System-Token']).not_to eq('existing_token')
+ end
+
+ it 'does not refresh token when System-Token header is absent' do
+ headers_without_token = auth_header.merge(version_header)
+
+ params = system.products.map do |product|
+ {
+ identifier: product.identifier,
+ version: product.version,
+ arch: product.arch,
+ release_type: product.release_type
+ }
+ end
+ post path,
+ params: { products: params },
+ headers: headers_without_token
+
+ expect(response.status).to eq(200)
+ expect(response.headers).not_to include('System-Token')
+ end
+ end
+ end
end
diff --git a/spec/services/repository_service_spec.rb b/spec/services/repository_service_spec.rb
index 1387ea3b0..0035395b5 100644
--- a/spec/services/repository_service_spec.rb
+++ b/spec/services/repository_service_spec.rb
@@ -88,7 +88,7 @@
it('is not custom') { expect(repository.custom?).to eq(true) }
- context 'already existing repositories with changing URL' do
+ context 'already existing repositories with changing URL', :skip_sqlite do
subject(:repository) do
service.create_repository!(product, url, attributes, custom: custom).reload
url = 'https://foo.bar.com/bar/foo'