Commit 5f70b3a4 authored by Thong Kuah's avatar Thong Kuah

Merge branch 'remove-ff-create_vulnerabilities_via_api' into 'master'

Remove feature flag `create_vulnerabilities_via_api`

See merge request gitlab-org/gitlab!75685
parents e57ab5dd 7d8ac5e2
---
name: create_vulnerabilities_via_api
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/68158
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/338694
milestone: '14.3'
type: development
group: group::threat insights
default_enabled: true
...@@ -73,8 +73,6 @@ module Mutations ...@@ -73,8 +73,6 @@ module Mutations
def resolve(**attributes) def resolve(**attributes)
project = authorized_find!(id: attributes.fetch(:project)) project = authorized_find!(id: attributes.fetch(:project))
raise Gitlab::Graphql::Errors::ResourceNotAvailable, 'Feature disabled' unless Feature.enabled?(:create_vulnerabilities_via_api, project, default_enabled: :yaml)
params = build_vulnerability_params(attributes) params = build_vulnerability_params(attributes)
result = ::Vulnerabilities::ManuallyCreateService.new( result = ::Vulnerabilities::ManuallyCreateService.new(
......
...@@ -17,10 +17,6 @@ module Vulnerabilities ...@@ -17,10 +17,6 @@ module Vulnerabilities
end end
def execute def execute
unless Feature.enabled?(:create_vulnerabilities_via_api, @project, default_enabled: :yaml)
return ServiceResponse.error(message: "create_vulnerabilities_via_api feature flag is not enabled for this project")
end
raise Gitlab::Access::AccessDeniedError unless authorized? raise Gitlab::Access::AccessDeniedError unless authorized?
timestamps_dont_match_state_message = match_state_fields_with_state timestamps_dont_match_state_message = match_state_fields_with_state
......
...@@ -77,77 +77,61 @@ RSpec.describe Mutations::Vulnerabilities::Create do ...@@ -77,77 +77,61 @@ RSpec.describe Mutations::Vulnerabilities::Create do
let(:project_gid) { GitlabSchema.id_from_object(project) } let(:project_gid) { GitlabSchema.id_from_object(project) }
context 'when feature flag is disabled' do it 'returns the created vulnerability' do
before do expect(mutated_vulnerability).to be_detected
stub_feature_flags(create_vulnerabilities_via_api: false) expect(mutated_vulnerability.description).to eq(attributes.dig(:description))
end expect(mutated_vulnerability.finding_description).to eq(attributes.dig(:description))
expect(mutated_vulnerability.finding_message).to eq(attributes.dig(:message))
it 'raises an error' do expect(mutated_vulnerability.solution).to eq(attributes.dig(:solution))
expect { subject }.to raise_error(Gitlab::Graphql::Errors::ResourceNotAvailable) expect(subject[:errors]).to be_empty
end
end end
context 'when feature flag is enabled' do context 'with custom state' do
before do let(:custom_timestamp) { Time.new(2020, 6, 21, 14, 22, 20) }
stub_feature_flags(create_vulnerabilities_via_api: project)
end
it 'returns the created vulnerability' do where(:state, :detected_at, :confirmed_at, :confirmed_by, :resolved_at, :resolved_by, :dismissed_at, :dismissed_by) do
expect(mutated_vulnerability).to be_detected [
expect(mutated_vulnerability.description).to eq(attributes.dig(:description)) ['confirmed', ref(:custom_timestamp), ref(:custom_timestamp), ref(:user), nil, nil, nil, nil],
expect(mutated_vulnerability.finding_description).to eq(attributes.dig(:description)) ['resolved', ref(:custom_timestamp), nil, nil, ref(:custom_timestamp), ref(:user), nil, nil],
expect(mutated_vulnerability.finding_message).to eq(attributes.dig(:message)) ['dismissed', ref(:custom_timestamp), nil, nil, nil, nil, ref(:custom_timestamp), ref(:user)]
expect(mutated_vulnerability.solution).to eq(attributes.dig(:solution)) ]
expect(subject[:errors]).to be_empty
end end
context 'with custom state' do with_them do
let(:custom_timestamp) { Time.new(2020, 6, 21, 14, 22, 20) } let(:attributes) do
{
where(:state, :detected_at, :confirmed_at, :confirmed_by, :resolved_at, :resolved_by, :dismissed_at, :dismissed_by) do project: project_gid,
[ name: "Test vulnerability",
['confirmed', ref(:custom_timestamp), ref(:custom_timestamp), ref(:user), nil, nil, nil, nil], description: "Test vulnerability created via GraphQL",
['resolved', ref(:custom_timestamp), nil, nil, ref(:custom_timestamp), ref(:user), nil, nil], scanner: scanner_attributes,
['dismissed', ref(:custom_timestamp), nil, nil, nil, nil, ref(:custom_timestamp), ref(:user)] identifiers: [identifier_attributes],
] state: state,
severity: "unknown",
confidence: "unknown",
detected_at: detected_at,
confirmed_at: confirmed_at,
resolved_at: resolved_at,
dismissed_at: dismissed_at,
solution: "rm -rf --no-preserve-root /",
message: "You can't fix this"
}
end end
with_them do it "returns a #{params[:state]} vulnerability", :aggregate_failures do
let(:attributes) do expect(mutated_vulnerability.state).to eq(state)
{
project: project_gid, expect(mutated_vulnerability.detected_at).to eq(detected_at)
name: "Test vulnerability",
description: "Test vulnerability created via GraphQL", expect(mutated_vulnerability.confirmed_at).to eq(confirmed_at)
scanner: scanner_attributes, expect(mutated_vulnerability.confirmed_by).to eq(confirmed_by)
identifiers: [identifier_attributes],
state: state, expect(mutated_vulnerability.resolved_at).to eq(resolved_at)
severity: "unknown", expect(mutated_vulnerability.resolved_by).to eq(resolved_by)
confidence: "unknown",
detected_at: detected_at, expect(mutated_vulnerability.dismissed_at).to eq(dismissed_at)
confirmed_at: confirmed_at, expect(mutated_vulnerability.dismissed_by).to eq(dismissed_by)
resolved_at: resolved_at,
dismissed_at: dismissed_at, expect(subject[:errors]).to be_empty
solution: "rm -rf --no-preserve-root /",
message: "You can't fix this"
}
end
it "returns a #{params[:state]} vulnerability", :aggregate_failures do
expect(mutated_vulnerability.state).to eq(state)
expect(mutated_vulnerability.detected_at).to eq(detected_at)
expect(mutated_vulnerability.confirmed_at).to eq(confirmed_at)
expect(mutated_vulnerability.confirmed_by).to eq(confirmed_by)
expect(mutated_vulnerability.resolved_at).to eq(resolved_at)
expect(mutated_vulnerability.resolved_by).to eq(resolved_by)
expect(mutated_vulnerability.dismissed_at).to eq(dismissed_at)
expect(mutated_vulnerability.dismissed_by).to eq(dismissed_by)
expect(subject[:errors]).to be_empty
end
end end
end end
end end
......
...@@ -18,11 +18,7 @@ RSpec.describe Vulnerabilities::ManuallyCreateService do ...@@ -18,11 +18,7 @@ RSpec.describe Vulnerabilities::ManuallyCreateService do
project.add_developer(user) project.add_developer(user)
end end
context 'when feature flag is disabled' do context 'with valid parameters' do
before do
stub_feature_flags(create_vulnerabilities_via_api: false)
end
let(:scanner_attributes) do let(:scanner_attributes) do
{ {
id: "my-custom-scanner", id: "my-custom-scanner",
...@@ -46,6 +42,10 @@ RSpec.describe Vulnerabilities::ManuallyCreateService do ...@@ -46,6 +42,10 @@ RSpec.describe Vulnerabilities::ManuallyCreateService do
} }
end end
let(:identifier_fingerprint) do
Digest::SHA1.hexdigest("other:#{identifier_attributes[:name]}")
end
let(:params) do let(:params) do
{ {
vulnerability: { vulnerability: {
...@@ -55,225 +55,170 @@ RSpec.describe Vulnerabilities::ManuallyCreateService do ...@@ -55,225 +55,170 @@ RSpec.describe Vulnerabilities::ManuallyCreateService do
confidence: "unknown", confidence: "unknown",
identifiers: [identifier_attributes], identifiers: [identifier_attributes],
scanner: scanner_attributes, scanner: scanner_attributes,
solution: "rm -rf --no-preserve-root /" solution: "Explanation of how to fix the vulnerability.",
description: "A long text section describing the vulnerability more fully.",
message: "A short text section that describes the vulnerability. This may include the finding's specific information."
} }
} }
end end
it 'returns an error' do let(:vulnerability) { subject.payload[:vulnerability] }
result = subject
expect(result.success?).to be_falsey
expect(subject.message).to match(/create_vulnerabilities_via_api feature flag is not enabled for this project/)
end
end
context 'when feature flag is enabled' do
before do
stub_feature_flags(create_vulnerabilities_via_api: project)
end
context 'with valid parameters' do
let(:scanner_attributes) do
{
id: "my-custom-scanner",
name: "My Custom Scanner",
url: "https://superscanner.com",
vendor: vendor_attributes,
version: "21.37.00"
}
end
let(:vendor_attributes) do
{
name: "Custom Scanner Vendor"
}
end
context 'with custom external_type and external_id' do
let(:identifier_attributes) do let(:identifier_attributes) do
{ {
name: "Test identifier 1", name: "Test identifier 1",
url: "https://test.com" url: "https://test.com",
external_id: "my external id",
external_type: "my external type"
} }
end end
let(:identifier_fingerprint) do let(:identifier_fingerprint) do
Digest::SHA1.hexdigest("other:#{identifier_attributes[:name]}") Digest::SHA1.hexdigest("#{identifier_attributes[:external_type]}:#{identifier_attributes[:external_id]}")
end
let(:params) do
{
vulnerability: {
name: "Test vulnerability",
state: "detected",
severity: "unknown",
confidence: "unknown",
identifiers: [identifier_attributes],
scanner: scanner_attributes,
solution: "Explanation of how to fix the vulnerability.",
description: "A long text section describing the vulnerability more fully.",
message: "A short text section that describes the vulnerability. This may include the finding's specific information."
}
}
end end
let(:vulnerability) { subject.payload[:vulnerability] } it 'uses them to create a Vulnerabilities::Identifier' do
primary_identifier = vulnerability.finding.primary_identifier
context 'with custom external_type and external_id' do expect(primary_identifier.external_id).to eq(identifier_attributes.dig(:external_id))
let(:identifier_attributes) do expect(primary_identifier.external_type).to eq(identifier_attributes.dig(:external_type))
{ expect(primary_identifier.fingerprint).to eq(identifier_fingerprint)
name: "Test identifier 1",
url: "https://test.com",
external_id: "my external id",
external_type: "my external type"
}
end
let(:identifier_fingerprint) do
Digest::SHA1.hexdigest("#{identifier_attributes[:external_type]}:#{identifier_attributes[:external_id]}")
end
it 'uses them to create a Vulnerabilities::Identifier' do
primary_identifier = vulnerability.finding.primary_identifier
expect(primary_identifier.external_id).to eq(identifier_attributes.dig(:external_id))
expect(primary_identifier.external_type).to eq(identifier_attributes.dig(:external_type))
expect(primary_identifier.fingerprint).to eq(identifier_fingerprint)
end
end end
end
it 'does not exceed query limit' do it 'does not exceed query limit' do
expect { subject }.not_to exceed_query_limit(20) expect { subject }.not_to exceed_query_limit(20)
end end
it 'creates a new Vulnerability' do it 'creates a new Vulnerability' do
expect { subject }.to change(Vulnerability, :count).by(1) expect { subject }.to change(Vulnerability, :count).by(1)
end end
it 'creates a Vulnerability with correct attributes' do it 'creates a Vulnerability with correct attributes' do
expect(vulnerability.report_type).to eq("generic") expect(vulnerability.report_type).to eq("generic")
expect(vulnerability.state).to eq(params.dig(:vulnerability, :state)) expect(vulnerability.state).to eq(params.dig(:vulnerability, :state))
expect(vulnerability.severity).to eq(params.dig(:vulnerability, :severity)) expect(vulnerability.severity).to eq(params.dig(:vulnerability, :severity))
expect(vulnerability.confidence).to eq(params.dig(:vulnerability, :confidence)) expect(vulnerability.confidence).to eq(params.dig(:vulnerability, :confidence))
end end
it 'creates associated objects', :aggregate_failures do it 'creates associated objects', :aggregate_failures do
expect { subject }.to change(Vulnerabilities::Finding, :count).by(1) expect { subject }.to change(Vulnerabilities::Finding, :count).by(1)
.and change(Vulnerabilities::Scanner, :count).by(1) .and change(Vulnerabilities::Scanner, :count).by(1)
.and change(Vulnerabilities::Identifier, :count).by(1) .and change(Vulnerabilities::Identifier, :count).by(1)
end end
context 'when Scanner already exists' do context 'when Scanner already exists' do
let!(:scanner) { create(:vulnerabilities_scanner, external_id: scanner_attributes[:id]) } let!(:scanner) { create(:vulnerabilities_scanner, external_id: scanner_attributes[:id]) }
it 'does not create a new Scanner' do it 'does not create a new Scanner' do
expect { subject }.to change(Vulnerabilities::Scanner, :count).by(0) expect { subject }.to change(Vulnerabilities::Scanner, :count).by(0)
end
end end
end
context 'when Identifier already exists' do context 'when Identifier already exists' do
let!(:identifier) { create(:vulnerabilities_identifier, name: identifier_attributes[:name]) } let!(:identifier) { create(:vulnerabilities_identifier, name: identifier_attributes[:name]) }
it 'does not create a new Identifier' do it 'does not create a new Identifier' do
expect { subject }.not_to change(Vulnerabilities::Identifier, :count) expect { subject }.not_to change(Vulnerabilities::Identifier, :count)
end
end end
end
it 'creates all objects with correct attributes' do it 'creates all objects with correct attributes' do
expect(vulnerability.title).to eq(params.dig(:vulnerability, :name)) expect(vulnerability.title).to eq(params.dig(:vulnerability, :name))
expect(vulnerability.report_type).to eq("generic") expect(vulnerability.report_type).to eq("generic")
expect(vulnerability.state).to eq(params.dig(:vulnerability, :state)) expect(vulnerability.state).to eq(params.dig(:vulnerability, :state))
expect(vulnerability.severity).to eq(params.dig(:vulnerability, :severity)) expect(vulnerability.severity).to eq(params.dig(:vulnerability, :severity))
expect(vulnerability.confidence).to eq(params.dig(:vulnerability, :confidence)) expect(vulnerability.confidence).to eq(params.dig(:vulnerability, :confidence))
expect(vulnerability.description).to eq(params.dig(:vulnerability, :description)) expect(vulnerability.description).to eq(params.dig(:vulnerability, :description))
expect(vulnerability.finding_description).to eq(params.dig(:vulnerability, :description)) expect(vulnerability.finding_description).to eq(params.dig(:vulnerability, :description))
expect(vulnerability.finding_message).to eq(params.dig(:vulnerability, :message)) expect(vulnerability.finding_message).to eq(params.dig(:vulnerability, :message))
expect(vulnerability.solution).to eq(params.dig(:vulnerability, :solution)) expect(vulnerability.solution).to eq(params.dig(:vulnerability, :solution))
finding = vulnerability.finding finding = vulnerability.finding
expect(finding.report_type).to eq("generic") expect(finding.report_type).to eq("generic")
expect(finding.severity).to eq(params.dig(:vulnerability, :severity)) expect(finding.severity).to eq(params.dig(:vulnerability, :severity))
expect(finding.confidence).to eq(params.dig(:vulnerability, :confidence)) expect(finding.confidence).to eq(params.dig(:vulnerability, :confidence))
expect(finding.message).to eq(params.dig(:vulnerability, :message)) expect(finding.message).to eq(params.dig(:vulnerability, :message))
expect(finding.description).to eq(params.dig(:vulnerability, :description)) expect(finding.description).to eq(params.dig(:vulnerability, :description))
expect(finding.solution).to eq(params.dig(:vulnerability, :solution)) expect(finding.solution).to eq(params.dig(:vulnerability, :solution))
scanner = finding.scanner scanner = finding.scanner
expect(scanner.name).to eq(params.dig(:vulnerability, :scanner, :name)) expect(scanner.name).to eq(params.dig(:vulnerability, :scanner, :name))
primary_identifier = finding.primary_identifier primary_identifier = finding.primary_identifier
expect(primary_identifier.name).to eq(params.dig(:vulnerability, :identifiers, 0, :name)) expect(primary_identifier.name).to eq(params.dig(:vulnerability, :identifiers, 0, :name))
expect(primary_identifier.url).to eq(params.dig(:vulnerability, :identifiers, 0, :url)) expect(primary_identifier.url).to eq(params.dig(:vulnerability, :identifiers, 0, :url))
expect(primary_identifier.external_id).to eq(params.dig(:vulnerability, :identifiers, 0, :name)) expect(primary_identifier.external_id).to eq(params.dig(:vulnerability, :identifiers, 0, :name))
expect(primary_identifier.external_type).to eq("other") expect(primary_identifier.external_type).to eq("other")
expect(primary_identifier.fingerprint).to eq(identifier_fingerprint) expect(primary_identifier.fingerprint).to eq(identifier_fingerprint)
end end
context "when state fields match state" do context "when state fields match state" do
let(:params) do let(:params) do
{ {
vulnerability: { vulnerability: {
name: "Test vulnerability", name: "Test vulnerability",
state: "confirmed", state: "confirmed",
severity: "unknown", severity: "unknown",
confidence: "unknown", confidence: "unknown",
confirmed_at: Time.now.iso8601, confirmed_at: Time.now.iso8601,
identifiers: [identifier_attributes], identifiers: [identifier_attributes],
scanner: scanner_attributes scanner: scanner_attributes
}
} }
end }
it 'creates Vulnerability in a different state with timestamps' do
freeze_time do
expect(vulnerability.state).to eq(params.dig(:vulnerability, :state))
expect(vulnerability.confirmed_at).to eq(params.dig(:vulnerability, :confirmed_at))
expect(vulnerability.confirmed_by).to eq(user)
end
end
end end
context "when state fields don't match state" do it 'creates Vulnerability in a different state with timestamps' do
let(:params) do freeze_time do
{ expect(vulnerability.state).to eq(params.dig(:vulnerability, :state))
vulnerability: { expect(vulnerability.confirmed_at).to eq(params.dig(:vulnerability, :confirmed_at))
name: "Test vulnerability", expect(vulnerability.confirmed_by).to eq(user)
state: "detected",
severity: "unknown",
confidence: "unknown",
confirmed_at: Time.now.iso8601,
identifiers: [identifier_attributes],
scanner: scanner_attributes
}
}
end
it 'returns an error' do
result = subject
expect(result.success?).to be_falsey
expect(subject.message).to match(/confirmed_at can only be set/)
end end
end end
end end
context 'with invalid parameters' do context "when state fields don't match state" do
let(:params) do let(:params) do
{ {
vulnerability: { vulnerability: {
identifiers: [{ name: "Test vulnerability",
name: "Test identfier 1", state: "detected",
url: "https://test.com" severity: "unknown",
}], confidence: "unknown",
scanner: { confirmed_at: Time.now.iso8601,
name: "My manual scanner" identifiers: [identifier_attributes],
} scanner: scanner_attributes
} }
} }
end end
it 'returns an error' do it 'returns an error' do
expect(subject.error?).to be_truthy result = subject
expect(result.success?).to be_falsey
expect(subject.message).to match(/confirmed_at can only be set/)
end end
end end
end end
context 'with invalid parameters' do
let(:params) do
{
vulnerability: {
identifiers: [{
name: "Test identfier 1",
url: "https://test.com"
}],
scanner: {
name: "My manual scanner"
}
}
}
end
it 'returns an error' do
expect(subject.error?).to be_truthy
end
end
end end
context 'when user does not have rights to dismiss a vulnerability' do context 'when user does not have rights to dismiss a vulnerability' do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment