Commit 10405e06 authored by Andreas Brandl's avatar Andreas Brandl

Merge branch '9224/create-mr-from-vulnerability-feedback' into 'master'

Create Merge Request From Remediated Vulnerability Solution

Closes #9224

See merge request gitlab-org/gitlab-ee!9326
parents 3935a41c 95de7bfe
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20190301081611) do ActiveRecord::Schema.define(version: 20190301182031) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -3181,8 +3181,10 @@ ActiveRecord::Schema.define(version: 20190301081611) do ...@@ -3181,8 +3181,10 @@ ActiveRecord::Schema.define(version: 20190301081611) do
t.integer "pipeline_id" t.integer "pipeline_id"
t.integer "issue_id" t.integer "issue_id"
t.string "project_fingerprint", limit: 40, null: false t.string "project_fingerprint", limit: 40, null: false
t.integer "merge_request_id"
t.index ["author_id"], name: "index_vulnerability_feedback_on_author_id", using: :btree t.index ["author_id"], name: "index_vulnerability_feedback_on_author_id", using: :btree
t.index ["issue_id"], name: "index_vulnerability_feedback_on_issue_id", using: :btree t.index ["issue_id"], name: "index_vulnerability_feedback_on_issue_id", using: :btree
t.index ["merge_request_id"], name: "index_vulnerability_feedback_on_merge_request_id", using: :btree
t.index ["pipeline_id"], name: "index_vulnerability_feedback_on_pipeline_id", using: :btree t.index ["pipeline_id"], name: "index_vulnerability_feedback_on_pipeline_id", using: :btree
t.index ["project_id", "category", "feedback_type", "project_fingerprint"], name: "vulnerability_feedback_unique_idx", unique: true, using: :btree t.index ["project_id", "category", "feedback_type", "project_fingerprint"], name: "vulnerability_feedback_unique_idx", unique: true, using: :btree
end end
...@@ -3600,6 +3602,7 @@ ActiveRecord::Schema.define(version: 20190301081611) do ...@@ -3600,6 +3602,7 @@ ActiveRecord::Schema.define(version: 20190301081611) do
add_foreign_key "users_star_projects", "projects", name: "fk_22cd27ddfc", on_delete: :cascade add_foreign_key "users_star_projects", "projects", name: "fk_22cd27ddfc", on_delete: :cascade
add_foreign_key "vulnerability_feedback", "ci_pipelines", column: "pipeline_id", on_delete: :nullify add_foreign_key "vulnerability_feedback", "ci_pipelines", column: "pipeline_id", on_delete: :nullify
add_foreign_key "vulnerability_feedback", "issues", on_delete: :nullify add_foreign_key "vulnerability_feedback", "issues", on_delete: :nullify
add_foreign_key "vulnerability_feedback", "merge_requests", name: "fk_563ff1912e", on_delete: :nullify
add_foreign_key "vulnerability_feedback", "projects", on_delete: :cascade add_foreign_key "vulnerability_feedback", "projects", on_delete: :cascade
add_foreign_key "vulnerability_feedback", "users", column: "author_id", on_delete: :cascade add_foreign_key "vulnerability_feedback", "users", column: "author_id", on_delete: :cascade
add_foreign_key "vulnerability_identifiers", "projects", on_delete: :cascade add_foreign_key "vulnerability_identifiers", "projects", on_delete: :cascade
......
...@@ -129,6 +129,9 @@ class Projects::VulnerabilityFeedbackController < Projects::ApplicationControlle ...@@ -129,6 +129,9 @@ class Projects::VulnerabilityFeedbackController < Projects::ApplicationControlle
links: %i[ links: %i[
name name
url url
],
remediations: %i[
diff
] ]
] ]
end end
......
...@@ -7,25 +7,27 @@ module Vulnerabilities ...@@ -7,25 +7,27 @@ module Vulnerabilities
belongs_to :project belongs_to :project
belongs_to :author, class_name: "User" belongs_to :author, class_name: "User"
belongs_to :issue belongs_to :issue
belongs_to :merge_request
belongs_to :pipeline, class_name: 'Ci::Pipeline', foreign_key: :pipeline_id belongs_to :pipeline, class_name: 'Ci::Pipeline', foreign_key: :pipeline_id
attr_accessor :vulnerability_data attr_accessor :vulnerability_data
enum feedback_type: { dismissal: 0, issue: 1 } enum feedback_type: { dismissal: 0, issue: 1, merge_request: 2 }
enum category: { sast: 0, dependency_scanning: 1, container_scanning: 2, dast: 3 } enum category: { sast: 0, dependency_scanning: 1, container_scanning: 2, dast: 3 }
validates :project, presence: true validates :project, presence: true
validates :author, presence: true validates :author, presence: true
validates :issue, presence: true, if: :issue? validates :issue, presence: true, if: :issue?
validates :vulnerability_data, presence: true, if: :issue? validates :merge_request, presence: true, if: :merge_request?
validates :vulnerability_data, presence: true, unless: :dismissal?
validates :feedback_type, presence: true validates :feedback_type, presence: true
validates :category, presence: true validates :category, presence: true
validates :project_fingerprint, presence: true, uniqueness: { scope: [:project_id, :category, :feedback_type] } validates :project_fingerprint, presence: true, uniqueness: { scope: [:project_id, :category, :feedback_type] }
scope :with_associations, -> { includes(:pipeline, :issue, :author) } scope :with_associations, -> { includes(:pipeline, :issue, :merge_request, :author) }
scope :all_preloaded, -> do scope :all_preloaded, -> do
preload(:author, :project, :issue, :pipeline) preload(:author, :project, :issue, :merge_request, :pipeline)
end end
end end
end end
...@@ -138,6 +138,10 @@ module Vulnerabilities ...@@ -138,6 +138,10 @@ module Vulnerabilities
feedback(feedback_type: 'issue') feedback(feedback_type: 'issue')
end end
def merge_request_feedback
feedback(feedback_type: 'merge_request')
end
def metadata def metadata
strong_memoize(:metadata) do strong_memoize(:metadata) do
begin begin
...@@ -163,5 +167,9 @@ module Vulnerabilities ...@@ -163,5 +167,9 @@ module Vulnerabilities
def links def links
metadata.fetch('links', []) metadata.fetch('links', [])
end end
def remediations
metadata.dig('remediations')
end
end end
end end
...@@ -18,13 +18,21 @@ class Vulnerabilities::FeedbackEntity < Grape::Entity ...@@ -18,13 +18,21 @@ class Vulnerabilities::FeedbackEntity < Grape::Entity
end end
expose :issue_iid, if: -> (feedback, _) { feedback.issue? } do |feedback| expose :issue_iid, if: -> (feedback, _) { feedback.issue? } do |feedback|
feedback.issue&.iid feedback.issue.iid
end end
expose :issue_url, if: -> (feedback, _) { feedback.issue? } do |feedback| expose :issue_url, if: -> (feedback, _) { feedback.issue? } do |feedback|
project_issue_url(feedback.project, feedback.issue) project_issue_url(feedback.project, feedback.issue)
end end
expose :merge_request_iid, if: -> (feedback, _) { feedback.merge_request? } do |feedback|
feedback.merge_request.iid
end
expose :merge_request_url, if: -> (feedback, _) { feedback.merge_request? } do |feedback|
project_merge_request_url(feedback.project, feedback.merge_request)
end
expose :category expose :category
expose :feedback_type expose :feedback_type
expose :branch do |feedback| expose :branch do |feedback|
......
...@@ -8,16 +8,19 @@ class Vulnerabilities::OccurrenceEntity < Grape::Entity ...@@ -8,16 +8,19 @@ class Vulnerabilities::OccurrenceEntity < Grape::Entity
expose :identifiers, using: Vulnerabilities::IdentifierEntity expose :identifiers, using: Vulnerabilities::IdentifierEntity
expose :project_fingerprint expose :project_fingerprint
expose :vulnerability_feedback_path, as: :vulnerability_feedback_issue_path, if: ->(_, _) { can_admin_vulnerability_feedback? && can_create_issue? } expose :vulnerability_feedback_path, as: :vulnerability_feedback_issue_path, if: ->(_, _) { can_admin_vulnerability_feedback? && can_create_issue? }
expose :vulnerability_feedback_path, as: :vulnerability_feedback_merge_request_path, if: ->(_, _) { can_admin_vulnerability_feedback? && can_create_merge_request? }
expose :vulnerability_feedback_path, as: :vulnerability_feedback_dismissal_path, if: ->(_, _) { can_admin_vulnerability_feedback? } expose :vulnerability_feedback_path, as: :vulnerability_feedback_dismissal_path, if: ->(_, _) { can_admin_vulnerability_feedback? }
expose :project, using: ::ProjectEntity expose :project, using: ::ProjectEntity
expose :dismissal_feedback, using: Vulnerabilities::FeedbackEntity expose :dismissal_feedback, using: Vulnerabilities::FeedbackEntity
expose :issue_feedback, using: Vulnerabilities::FeedbackEntity expose :issue_feedback, using: Vulnerabilities::FeedbackEntity
expose :merge_request_feedback, using: Vulnerabilities::FeedbackEntity
expose :metadata, merge: true, if: ->(occurrence, _) { occurrence.raw_metadata } do expose :metadata, merge: true, if: ->(occurrence, _) { occurrence.raw_metadata } do
expose :description expose :description
expose :solution
expose :location
expose :links expose :links
expose :location
expose :remediations
expose :solution
end end
alias_method :occurrence, :object alias_method :occurrence, :object
...@@ -35,4 +38,8 @@ class Vulnerabilities::OccurrenceEntity < Grape::Entity ...@@ -35,4 +38,8 @@ class Vulnerabilities::OccurrenceEntity < Grape::Entity
def can_create_issue? def can_create_issue?
can?(request.current_user, :create_issue, occurrence.project) can?(request.current_user, :create_issue, occurrence.project)
end end
def can_create_merge_request?
can?(request.current_user, :create_merge_request_in, occurrence.project)
end
end end
# frozen_string_literal: true
module MergeRequests
class CreateFromVulnerabilityDataService < ::BaseService
def execute
vulnerability = create_vulnerability
return error('Invalid vulnerability category') unless vulnerability
title_slug = Gitlab::Utils.slugify(vulnerability.title)
source_branch = "remediate/%s-%s" % [
title_slug[0..74],
Time.now.strftime("D%Y%m%dT%H%M%S")
]
target_branch = @project.default_branch
return error("Can't create merge_request") unless can_create_merge_request?(source_branch)
return error("Can't create merge request") if vulnerability.remediations.empty?
patch_result = create_patch(vulnerability, source_branch, target_branch)
return error('Unable to apply patch') unless patch_result[:status] == :success
merge_request_params = {
title: "Resolve vulnerability: #{vulnerability.title}",
description: render_description(vulnerability),
source_branch: source_branch,
target_branch: target_branch,
force_remove_source_branch: "1"
}
merge_request = MergeRequests::CreateService.new(@project, @current_user, merge_request_params).execute
if merge_request.valid?
success(merge_request)
else
error(merge_request.errors)
end
end
private
def create_vulnerability
case @params[:category]
when 'sast', 'dependency_scanning', 'dast'
Gitlab::Vulnerabilities::StandardVulnerability.new(@params)
when 'container_scanning'
Gitlab::Vulnerabilities::ContainerScanningVulnerability.new(@params)
end
end
def create_patch(vulnerability, source_branch, target_branch)
diffs = vulnerability.data[:remediations].map { |remediation| Base64.decode64(remediation[:diff]) }
head_commit = project.repository.find_branch(target_branch).dereferenced_target
new_commit = render_commit(diffs, head_commit, vulnerability)
commit_patch_params = {
branch_name: source_branch,
patches: [
new_commit
]
}
Commits::CommitPatchService.new(@project, @current_user, commit_patch_params).execute
end
def success(merge_request)
super(merge_request: merge_request)
end
def render_commit(diffs, head_commit, vulnerability)
git_user = Gitlab::Git::User.from_gitlab(@current_user)
# This currently applies only the first diff
render_template(
file: 'vulnerabilities/remediation.patch.erb',
locals: {
diff: diffs.first,
head_commit: head_commit,
user: git_user,
vulnerability: vulnerability
}
)
end
def render_description(vulnerability)
render_template(
file: 'vulnerabilities/merge_request_description.md.erb',
locals: { vulnerability: vulnerability }
)
end
def render_template(file:, locals:)
view = ActionView::Base.new(ActionController::Base.view_paths, {})
view.render(file: file, locals: locals)
end
def can_create_merge_request?(source_branch)
can?(@current_user, :create_merge_request_in, @project) &&
can?(@current_user, :create_merge_request_from, @project) &&
::Gitlab::UserAccess.new(@current_user, project: @project).can_push_to_branch?(source_branch)
end
end
end
...@@ -6,31 +6,23 @@ module VulnerabilityFeedbackModule ...@@ -6,31 +6,23 @@ module VulnerabilityFeedbackModule
vulnerability_feedback = @project.vulnerability_feedback.new(@params) vulnerability_feedback = @project.vulnerability_feedback.new(@params)
vulnerability_feedback.author = @current_user vulnerability_feedback.author = @current_user
# Wrap Feedback and Issue creation in the same transaction if vulnerability_feedback.issue? &&
ActiveRecord::Base.transaction do !vulnerability_feedback.vulnerability_data.blank?
if vulnerability_feedback.issue? && # (feedback_type == 'issue')
!vulnerability_feedback.vulnerability_data.blank?
result = Issues::CreateFromVulnerabilityDataService create_issue_for(vulnerability_feedback)
.new(@project, @current_user, vulnerability_feedback.vulnerability_data) elsif vulnerability_feedback.merge_request? &&
.execute !vulnerability_feedback.vulnerability_data.blank?
if result[:status] == :error create_merge_request_for(vulnerability_feedback)
vulnerability_feedback.errors[:issue] << result[:message] else
raise ActiveRecord::Rollback vulnerability_feedback.save
end
issue = result[:issue]
vulnerability_feedback.issue = issue
end
# Ensure created issue is rolled back if feedback can't be saved
raise ActiveRecord::Rollback unless vulnerability_feedback.save
end end
if vulnerability_feedback.persisted? if vulnerability_feedback.persisted?
success(vulnerability_feedback) success(vulnerability_feedback)
else else
rollback_merge_request(vulnerability_feedback.merge_request) if vulnerability_feedback.merge_request
error(vulnerability_feedback.errors) error(vulnerability_feedback.errors)
end end
...@@ -44,5 +36,49 @@ module VulnerabilityFeedbackModule ...@@ -44,5 +36,49 @@ module VulnerabilityFeedbackModule
def success(vulnerability_feedback) def success(vulnerability_feedback)
super().merge(vulnerability_feedback: vulnerability_feedback) super().merge(vulnerability_feedback: vulnerability_feedback)
end end
def create_issue_for(vulnerability_feedback)
# Wrap Feedback and Issue creation in the same transaction
ActiveRecord::Base.transaction do
result = Issues::CreateFromVulnerabilityDataService
.new(@project, @current_user, vulnerability_feedback.vulnerability_data)
.execute
if result[:status] == :error
vulnerability_feedback.errors[:issue] << result[:message]
raise ActiveRecord::Rollback
end
issue = result[:issue]
vulnerability_feedback.issue = issue
# Ensure created association is rolled back if feedback can't be saved
raise ActiveRecord::Rollback unless vulnerability_feedback.save
end
end
def create_merge_request_for(vulnerability_feedback)
result = MergeRequests::CreateFromVulnerabilityDataService
.new(@project, @current_user, vulnerability_feedback.vulnerability_data)
.execute
if result[:status] == :success
merge_request = result[:merge_request]
vulnerability_feedback.merge_request = merge_request
vulnerability_feedback.save
else
vulnerability_feedback.errors[:merge_request] << result[:message]
end
end
# Gitaly RPCs cannot occur within a transaction so we must manually
# rollback MR and branch creation
def rollback_merge_request(merge_request)
branch_name = merge_request.source_branch
merge_request&.destroy &&
DeleteBranchService.new(project, current_user).execute(branch_name)
end
end end
end end
### Description:
<%= vulnerability.description %>
<% if vulnerability.severity.present? %>
* Severity: <%= vulnerability.severity %>
<% end %>
<% if vulnerability.confidence.present? %>
* Confidence: <%= vulnerability.confidence %>
<% end %>
<% if defined?(vulnerability.file) && vulnerability.file.present? %>
* Location: [<%= vulnerability.location_text %>](<%= vulnerability.location_link %>)
<% end %>
<% if vulnerability.solution.present? %>
### Solution:
<%= vulnerability.solution %>
<% end %>
<% if vulnerability.identifiers.present? %>
### Identifiers:
<% vulnerability.identifiers.each do |identifier| %>
<% if identifier[:url].present? %>
* [<%= identifier[:name] %>](<%= identifier[:url] %>)
<% else %>
* <%= identifier[:name] %>
<% end %>
<% end %>
<% end %>
<% if vulnerability.links.present? %>
### Links:
<% vulnerability.links.each do |link| %>
<% if link[:name].present? %>
* [<%= link[:name] %>](<%= link[:url] %>)
<% else %>
* <%= link[:url] %>
<% end %>
<% end %>
<% end %>
From <%= head_commit.sha %> <%= head_commit.date.strftime("%a %b %e %H:%M:%S %Y") %>
From: <%= user.name %> <<%= user.email %>>
Date: <%= Time.now.strftime("%a, %e %b %Y %H:%M:%S %z") %>
Subject: Fix Vulnerability - <%= vulnerability.title %>
<%= diff %>
--
2.19.1
\ No newline at end of file
---
title: Create MR from Vulnerability Solution
merge_request: 9326
author:
type: added
# frozen_string_literal: true
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddMergeRequestIdToVulnerabilityFeedback < ActiveRecord::Migration[5.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def up
add_column :vulnerability_feedback, :merge_request_id, :integer, null: true
end
def down
remove_column :vulnerability_feedback, :merge_request_id, :integer
end
end
# frozen_string_literal: true
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddMergeRequestIdIndexOnVulnerabilityFeedback < ActiveRecord::Migration[5.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def change
add_concurrent_index :vulnerability_feedback, :merge_request_id
add_concurrent_foreign_key :vulnerability_feedback, :merge_requests, column: :merge_request_id, on_delete: :nullify
end
end
...@@ -11,7 +11,7 @@ module Gitlab ...@@ -11,7 +11,7 @@ module Gitlab
report_data = parse_report(json_data) report_data = parse_report(json_data)
raise SecurityReportParserError, "Invalid report format" unless report_data.is_a?(Hash) raise SecurityReportParserError, "Invalid report format" unless report_data.is_a?(Hash)
report_data["vulnerabilities"].each do |vulnerability| collate_remediations(report_data).each do |vulnerability|
create_vulnerability(report, vulnerability, report_data["version"]) create_vulnerability(report, vulnerability, report_data["version"])
end end
rescue JSON::ParserError rescue JSON::ParserError
...@@ -27,6 +27,20 @@ module Gitlab ...@@ -27,6 +27,20 @@ module Gitlab
JSON.parse!(json_data) JSON.parse!(json_data)
end end
# map remediations to relevant vulnerabilities
def collate_remediations(report_data)
return report_data["vulnerabilities"] unless report_data["remediations"]
report_data["vulnerabilities"].map do |vulnerability|
# Grab the first available remediation.
remediation = report_data["remediations"].find do |remediation|
remediation["fixes"].any? { |fix| fix["cve"] == vulnerability["cve"] }
end
vulnerability.merge("remediations" => [remediation])
end
end
def create_vulnerability(report, data, version) def create_vulnerability(report, data, version)
scanner = create_scanner(report, data['scanner'] || mutate_scanner_tool(data['tool'])) scanner = create_scanner(report, data['scanner'] || mutate_scanner_tool(data['tool']))
identifiers = create_identifiers(report, data['identifiers']) identifiers = create_identifiers(report, data['identifiers'])
......
...@@ -10,6 +10,7 @@ module Gitlab ...@@ -10,6 +10,7 @@ module Gitlab
solution solution
identifiers identifiers
links links
remediations
].each do |method_name| ].each do |method_name|
define_method(method_name) do define_method(method_name) do
@data[method_name] @data[method_name]
......
...@@ -15,11 +15,13 @@ describe Projects::VulnerabilityFeedbackController do ...@@ -15,11 +15,13 @@ describe Projects::VulnerabilityFeedbackController do
let(:pipeline_2) { create(:ci_pipeline, project: project) } let(:pipeline_2) { create(:ci_pipeline, project: project) }
let(:issue) { create(:issue, project: project) } let(:issue) { create(:issue, project: project) }
let(:merge_request) { create(:merge_request, source_project: project) }
let!(:vuln_feedback_1) { create(:vulnerability_feedback, :dismissal, :sast, project: project, author: user, pipeline: pipeline_1) } let!(:vuln_feedback_1) { create(:vulnerability_feedback, :dismissal, :sast, project: project, author: user, pipeline: pipeline_1) }
let!(:vuln_feedback_2) { create(:vulnerability_feedback, :issue, :sast, project: project, author: user, pipeline: pipeline_1, issue: issue) } let!(:vuln_feedback_2) { create(:vulnerability_feedback, :issue, :sast, project: project, author: user, pipeline: pipeline_1, issue: issue) }
let!(:vuln_feedback_3) { create(:vulnerability_feedback, :dismissal, :sast, project: project, author: user, pipeline: pipeline_2) } let!(:vuln_feedback_3) { create(:vulnerability_feedback, :dismissal, :sast, project: project, author: user, pipeline: pipeline_2) }
let!(:vuln_feedback_4) { create(:vulnerability_feedback, :dismissal, :dependency_scanning, project: project, author: user, pipeline: pipeline_2) } let!(:vuln_feedback_4) { create(:vulnerability_feedback, :dismissal, :dependency_scanning, project: project, author: user, pipeline: pipeline_2) }
let!(:vuln_feedback_5) { create(:vulnerability_feedback, :merge_request, :dependency_scanning, project: project, author: user, pipeline: pipeline_1, merge_request: merge_request) }
context '@vulnerability_feedback' do context '@vulnerability_feedback' do
it 'returns a successful 200 response' do it 'returns a successful 200 response' do
...@@ -32,7 +34,7 @@ describe Projects::VulnerabilityFeedbackController do ...@@ -32,7 +34,7 @@ describe Projects::VulnerabilityFeedbackController do
list_feedbacks list_feedbacks
expect(response).to match_response_schema('vulnerability_feedback_list', dir: 'ee') expect(response).to match_response_schema('vulnerability_feedback_list', dir: 'ee')
expect(json_response.length).to eq 4 expect(json_response.length).to eq 5
end end
context 'with filter params' do context 'with filter params' do
......
...@@ -92,6 +92,16 @@ FactoryBot.define do ...@@ -92,6 +92,16 @@ FactoryBot.define do
end end
end end
trait :dependency_scanning_remediation do
file_format :raw
file_type :dependency_scanning
after(:build) do |artifact, _|
artifact.file = fixture_file_upload(
Rails.root.join('spec/fixtures/security-reports/remediations/gl-dependency-scanning-report.json'), 'text/plain')
end
end
trait :dependency_scanning_deprecated do trait :dependency_scanning_deprecated do
file_format :raw file_format :raw
file_type :dependency_scanning file_type :dependency_scanning
......
...@@ -9,6 +9,7 @@ FactoryBot.define do ...@@ -9,6 +9,7 @@ FactoryBot.define do
project project
author author
issue nil issue nil
merge_request nil
association :pipeline, factory: :ci_pipeline association :pipeline, factory: :ci_pipeline
feedback_type 'dismissal' feedback_type 'dismissal'
category 'sast' category 'sast'
...@@ -23,6 +24,11 @@ FactoryBot.define do ...@@ -23,6 +24,11 @@ FactoryBot.define do
feedback_type 'issue' feedback_type 'issue'
end end
trait :merge_request do
feedback_type 'merge_request'
merge_request { create(:merge_request, source_project: project) }
end
trait :sast do trait :sast do
category 'sast' category 'sast'
end end
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
"name" : { "type": "string" }, "name" : { "type": "string" },
"project_fingerprint": { "type": "string" }, "project_fingerprint": { "type": "string" },
"vulnerability_feedback_issue_path": { "type": "string" }, "vulnerability_feedback_issue_path": { "type": "string" },
"vulnerability_feedback_merge_request_path": { "type": "string" },
"vulnerability_feedback_dismissal_path": { "type": "string" }, "vulnerability_feedback_dismissal_path": { "type": "string" },
"confidence" : { "confidence" : {
"type": "string", "type": "string",
...@@ -46,6 +47,10 @@ ...@@ -46,6 +47,10 @@
{ "type": "null" }, { "type": "null" },
{ "$ref": "../vulnerability_feedback.json" } { "$ref": "../vulnerability_feedback.json" }
]}, ]},
"merge_request_feedback" : { "oneOf": [
{ "type": "null" },
{ "$ref": "../vulnerability_feedback.json" }
]},
"dismissal_feedback" : { "oneOf": [ "dismissal_feedback" : { "oneOf": [
{ "type": "null" }, { "type": "null" },
{ "$ref": "../vulnerability_feedback.json" } { "$ref": "../vulnerability_feedback.json" }
......
...@@ -18,9 +18,11 @@ ...@@ -18,9 +18,11 @@
}, },
"issue_iid": { "type": ["integer", "null"] }, "issue_iid": { "type": ["integer", "null"] },
"issue_url": { "type": ["string", "null"] }, "issue_url": { "type": ["string", "null"] },
"merge_request_iid": { "type": ["integer", "null"] },
"merge_request_url": { "type": ["string", "null"] },
"feedback_type": { "feedback_type": {
"type": "string", "type": "string",
"enum": ["dismissal", "issue"] "enum": ["dismissal", "issue", "merge_request"]
}, },
"category": { "category": {
"type": "string", "type": "string",
......
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
require 'spec_helper' require 'spec_helper'
describe Gitlab::Ci::Parsers::Security::DependencyScanning do describe Gitlab::Ci::Parsers::Security::DependencyScanning do
using RSpec::Parameterized::TableSyntax
describe '#parse!' do describe '#parse!' do
let(:project) { artifact.project } let(:project) { artifact.project }
let(:pipeline) { artifact.job.pipeline } let(:pipeline) { artifact.job.pipeline }
...@@ -10,7 +12,11 @@ describe Gitlab::Ci::Parsers::Security::DependencyScanning do ...@@ -10,7 +12,11 @@ describe Gitlab::Ci::Parsers::Security::DependencyScanning do
let(:report) { Gitlab::Ci::Reports::Security::Report.new(artifact.file_type) } let(:report) { Gitlab::Ci::Reports::Security::Report.new(artifact.file_type) }
let(:parser) { described_class.new } let(:parser) { described_class.new }
where(report_format: %i(dependency_scanning dependency_scanning_deprecated)) where(:report_format, :occurrence_count, :identifier_count, :scanner_count, :fingerprint, :version) do
:dependency_scanning | 4 | 7 | 2 | '2773f8cc955346ab1f756b94aa310db8e17c0944' | '1.3'
:dependency_scanning_deprecated | 4 | 7 | 2 | '2773f8cc955346ab1f756b94aa310db8e17c0944' | '1.3'
:dependency_scanning_remediation | 2 | 3 | 1 | '228998b5db51d86d3b091939e2f5873ada0a14a1' | '2.0'
end
with_them do with_them do
let(:artifact) { create(:ee_ci_job_artifact, report_format) } let(:artifact) { create(:ee_ci_job_artifact, report_format) }
...@@ -22,17 +28,36 @@ describe Gitlab::Ci::Parsers::Security::DependencyScanning do ...@@ -22,17 +28,36 @@ describe Gitlab::Ci::Parsers::Security::DependencyScanning do
end end
it "parses all identifiers and occurrences" do it "parses all identifiers and occurrences" do
expect(report.occurrences.length).to eq(4) expect(report.occurrences.length).to eq(occurrence_count)
expect(report.identifiers.length).to eq(7) expect(report.identifiers.length).to eq(identifier_count)
expect(report.scanners.length).to eq(2) expect(report.scanners.length).to eq(scanner_count)
end end
it "generates expected location fingerprint" do it "generates expected location fingerprint" do
expect(report.occurrences.first[:location_fingerprint]).to eq('2773f8cc955346ab1f756b94aa310db8e17c0944') expect(report.occurrences.first[:location_fingerprint]).to eq(fingerprint)
end end
it "generates expected metadata_version" do it "generates expected metadata_version" do
expect(report.occurrences.first[:metadata_version]).to eq('1.3') expect(report.occurrences.first[:metadata_version]).to eq(version)
end
end
context "when vulnerabilities have remediations" do
let(:artifact) { create(:ee_ci_job_artifact, :dependency_scanning_remediation) }
before do
artifact.each_blob do |blob|
parser.parse!(blob, report)
end
end
it "generates occurrence with expected remediation" do
occurrence = report.occurrences.last
raw_metadata = JSON.parse!(occurrence[:raw_metadata])
expect(occurrence[:name]).to eq("Authentication bypass via incorrect DOM traversal and canonicalization in saml2-js")
expect(raw_metadata["remediations"].first["summary"]).to eq("Upgrade saml2-js")
expect(raw_metadata["remediations"].first["diff"]).to start_with("ZGlmZiAtLWdpdCBhL3lhcm4")
end end
end end
end end
......
...@@ -10,6 +10,7 @@ describe Vulnerabilities::Feedback do ...@@ -10,6 +10,7 @@ describe Vulnerabilities::Feedback do
it { is_expected.to belong_to(:project) } it { is_expected.to belong_to(:project) }
it { is_expected.to belong_to(:author).class_name('User') } it { is_expected.to belong_to(:author).class_name('User') }
it { is_expected.to belong_to(:issue) } it { is_expected.to belong_to(:issue) }
it { is_expected.to belong_to(:merge_request) }
it { is_expected.to belong_to(:pipeline).class_name('Ci::Pipeline').with_foreign_key('pipeline_id') } it { is_expected.to belong_to(:pipeline).class_name('Ci::Pipeline').with_foreign_key('pipeline_id') }
end end
......
...@@ -230,4 +230,81 @@ describe Vulnerabilities::Occurrence do ...@@ -230,4 +230,81 @@ describe Vulnerabilities::Occurrence do
is_expected.to eq({ 4 => 1, 5 => 2, 6 => 3 }) is_expected.to eq({ 4 => 1, 5 => 2, 6 => 3 })
end end
end end
describe 'feedback' do
set(:project) { create(:project) }
let(:occurrence) do
create(
:vulnerabilities_occurrence,
report_type: :dependency_scanning,
project: project
)
end
describe '#issue_feedback' do
let(:issue) { create(:issue, project: project) }
let!(:issue_feedback) do
create(
:vulnerability_feedback,
:dependency_scanning,
:issue,
issue: issue,
project: project,
project_fingerprint: occurrence.project_fingerprint
)
end
it 'returns associated feedback' do
feedback = occurrence.issue_feedback
expect(feedback).to be_present
expect(feedback[:project_id]).to eq project.id
expect(feedback[:feedback_type]).to eq 'issue'
expect(feedback[:issue_id]).to eq issue.id
end
end
describe '#dismissal_feedback' do
let!(:dismissal_feedback) do
create(
:vulnerability_feedback,
:dependency_scanning,
:dismissal,
project: project,
project_fingerprint: occurrence.project_fingerprint
)
end
it 'returns associated feedback' do
feedback = occurrence.dismissal_feedback
expect(feedback).to be_present
expect(feedback[:project_id]).to eq project.id
expect(feedback[:feedback_type]).to eq 'dismissal'
end
end
describe '#merge_request_feedback' do
let(:merge_request) { create(:merge_request, source_project: project) }
let!(:merge_request_feedback) do
create(
:vulnerability_feedback,
:dependency_scanning,
:merge_request,
merge_request: merge_request,
project: project,
project_fingerprint: occurrence.project_fingerprint
)
end
it 'returns associated feedback' do
feedback = occurrence.merge_request_feedback
expect(feedback).to be_present
expect(feedback[:project_id]).to eq project.id
expect(feedback[:feedback_type]).to eq 'merge_request'
expect(feedback[:merge_request_id]).to eq merge_request.id
end
end
end
end end
...@@ -53,7 +53,7 @@ describe Vulnerabilities::OccurrenceEntity do ...@@ -53,7 +53,7 @@ describe Vulnerabilities::OccurrenceEntity do
expect(subject).to include(:name, :report_type, :severity, :confidence, :project_fingerprint) expect(subject).to include(:name, :report_type, :severity, :confidence, :project_fingerprint)
expect(subject).to include(:scanner, :project, :identifiers) expect(subject).to include(:scanner, :project, :identifiers)
expect(subject).to include(:dismissal_feedback, :issue_feedback) expect(subject).to include(:dismissal_feedback, :issue_feedback)
expect(subject).to include(:description, :solution, :location, :links) expect(subject).to include(:description, :links, :location, :remediations, :solution)
end end
context 'when not allowed to admin vulnerability feedback' do context 'when not allowed to admin vulnerability feedback' do
...@@ -63,6 +63,7 @@ describe Vulnerabilities::OccurrenceEntity do ...@@ -63,6 +63,7 @@ describe Vulnerabilities::OccurrenceEntity do
it 'does not contain vulnerability feedback paths' do it 'does not contain vulnerability feedback paths' do
expect(subject).not_to include(:vulnerability_feedback_issue_path) expect(subject).not_to include(:vulnerability_feedback_issue_path)
expect(subject).not_to include(:vulnerability_feedback_merge_request_path)
expect(subject).not_to include(:vulnerability_feedback_dismissal_path) expect(subject).not_to include(:vulnerability_feedback_dismissal_path)
end end
end end
...@@ -80,6 +81,10 @@ describe Vulnerabilities::OccurrenceEntity do ...@@ -80,6 +81,10 @@ describe Vulnerabilities::OccurrenceEntity do
expect(subject).to include(:vulnerability_feedback_issue_path) expect(subject).to include(:vulnerability_feedback_issue_path)
end end
it 'contains vulnerability feedback merge_request path' do
expect(subject).to include(:vulnerability_feedback_merge_request_path)
end
context 'when disallowed to create issue' do context 'when disallowed to create issue' do
let(:project) { create(:project, issues_access_level: ProjectFeature::DISABLED) } let(:project) { create(:project, issues_access_level: ProjectFeature::DISABLED) }
...@@ -90,6 +95,26 @@ describe Vulnerabilities::OccurrenceEntity do ...@@ -90,6 +95,26 @@ describe Vulnerabilities::OccurrenceEntity do
it 'contains vulnerability feedback dismissal path' do it 'contains vulnerability feedback dismissal path' do
expect(subject).to include(:vulnerability_feedback_dismissal_path) expect(subject).to include(:vulnerability_feedback_dismissal_path)
end end
it 'contains vulnerability feedback merge_request path' do
expect(subject).to include(:vulnerability_feedback_merge_request_path)
end
end
context 'when disallowed to create merge_request' do
let(:project) { create(:project, merge_requests_access_level: ProjectFeature::DISABLED) }
it 'does not contain vulnerability feedback merge_request path' do
expect(subject).not_to include(:vulnerability_feedback_merge_request_path)
end
it 'contains vulnerability feedback issue path' do
expect(subject).to include(:vulnerability_feedback_issue_path)
end
it 'contains vulnerability feedback dismissal path' do
expect(subject).to include(:vulnerability_feedback_dismissal_path)
end
end end
end end
end end
......
# frozen_string_literal: true
require 'spec_helper'
describe MergeRequests::CreateFromVulnerabilityDataService, '#execute' do
let(:remediations_folder) { Rails.root.join('spec/fixtures/security-reports/remediations') }
let(:yarn_lock_content) { File.read(File.join(remediations_folder, "yarn.lock")) }
let(:remediation_patch_content) { File.read(File.join(remediations_folder, "remediation.patch")) }
let(:group) { create(:group) }
let(:user) { create(:user) }
let(:project) do
create(:project, :custom_repo, namespace: group, files: { 'yarn.lock' => yarn_lock_content })
end
before do
group.add_developer(user)
end
shared_examples 'a created merge_request' do
let(:result) { described_class.new(project, user, params).execute }
it 'creates the merge_request with the given params' do
expect(result[:status]).to eq(:success)
merge_request = result[:merge_request]
expect(merge_request).to be_persisted
expect(merge_request.project).to eq(project)
expect(merge_request.author).to eq(user)
expect(merge_request.title).to eq(expected_title)
expect(merge_request.description).to eq(expected_description)
expect(merge_request.target_branch).to eq(project.default_branch)
expect(merge_request.source_branch).to start_with('remediate/authentication-bypass-via-incorrect-dom-traversal-and-canonical')
end
end
context 'when user does not have permission to create merge_request' do
let(:result) { described_class.new(project, user, { title: 'title', category: 'dependency_scanning' }).execute }
before do
allow_any_instance_of(described_class).to receive(:can?).with(user, :create_merge_request_in, project).and_return(false)
allow_any_instance_of(described_class).to receive(:can?).with(user, :create_merge_request_from, project).and_return(false)
end
it 'returns expected error' do
expect(result[:status]).to eq(:error)
expect(result[:message]).to eq("Can't create merge_request")
end
end
context 'when merge_requests are disabled on project' do
let(:result) { described_class.new(project, user, { title: 'title', category: 'dependency_scanning' }).execute }
let(:project) { create(:project, namespace: group, merge_requests_access_level: ProjectFeature::DISABLED) }
it 'returns expected error' do
expect(result[:status]).to eq(:error)
expect(result[:message]).to eq("Can't create merge_request")
end
end
context 'when params are valid' do
context 'when category is dependency scanning' do
let(:diff) do
Base64.encode64(remediation_patch_content)
end
context 'when a description is present' do
let(:params) do
{
category: "dependency_scanning",
name: "Authentication bypass via incorrect DOM traversal and canonicalization",
message: "Authentication bypass via incorrect DOM traversal and canonicalization in saml2-js",
description: "Some XML DOM traversal and canonicalization APIs may be inconsistent in handling of comments within XML nodes. Incorrect use of these APIs by some SAML libraries results in incorrect parsing of the inner text of XML nodes such that any inner text after the comment is lost prior to cryptographically signing the SAML message. Text after the comment therefore has no impact on the signature on the SAML message.\n\nA remote attacker can modify SAML content for a SAML service provider without invalidating the cryptographic signature, which may allow attackers to bypass primary authentication for the affected SAML service provider.",
cve: "yarn.lock:saml2-js:gemnasium:9952e574-7b5b-46fa-a270-aeb694198a98",
severity: "Unknown",
solution: "Upgrade to fixed version.",
scanner: {
id: "gemnasium",
name: "Gemnasium"
},
location: {
file: "yarn.lock",
dependency: {
package: {
name: "saml2-js"
},
version: "1.5.0"
}
},
identifiers: [
{
type: "gemnasium",
name: "Gemnasium-9952e574-7b5b-46fa-a270-aeb694198a98",
value: "9952e574-7b5b-46fa-a270-aeb694198a98",
url: "https://deps.sec.gitlab.com/packages/npm/saml2-js/versions/1.5.0/advisories"
},
{
type: "cve",
name: "CVE-2017-11429",
value: "CVE-2017-11429",
url: "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-11429"
}
],
links: [
{
url: "https://github.com/Clever/saml2/commit/3546cb61fd541f219abda364c5b919633609ef3d#diff-af730f9f738de1c9ad87596df3f6de84R279"
},
{
url: "https://github.com/Clever/saml2/issues/127"
},
{
url: "https://www.kb.cert.org/vuls/id/475445"
}
],
project_fingerprint: "fa6f5b6c5d240b834ac5e901dc69f9484cef89ec",
title: "Authentication bypass via incorrect DOM traversal and canonicalization in saml2-js",
remediations: [
{
fixes: [
{
cve: "yarn.lock:saml2-js:gemnasium:9952e574-7b5b-46fa-a270-aeb694198a98"
}
],
summary: "Upgrade saml2-js",
diff: diff
}
],
path: "yarn.lock",
urlPath: "/root/yarn-remediation/blob/9bade3fef995a9e2ca1de44d396b433864990ab9/yarn.lock"
}
end
let(:expected_title) { 'Resolve vulnerability: Authentication bypass via incorrect DOM traversal and canonicalization in saml2-js' }
let(:expected_description) do
<<~DESC.chomp
### Description:
Some XML DOM traversal and canonicalization APIs may be inconsistent in handling of comments within XML nodes. Incorrect use of these APIs by some SAML libraries results in incorrect parsing of the inner text of XML nodes such that any inner text after the comment is lost prior to cryptographically signing the SAML message. Text after the comment therefore has no impact on the signature on the SAML message.\n\nA remote attacker can modify SAML content for a SAML service provider without invalidating the cryptographic signature, which may allow attackers to bypass primary authentication for the affected SAML service provider.
* Severity: Unknown
* Location: [yarn.lock](yarn.lock)
### Solution:
Upgrade to fixed version.
### Identifiers:
* [Gemnasium-9952e574-7b5b-46fa-a270-aeb694198a98](https://deps.sec.gitlab.com/packages/npm/saml2-js/versions/1.5.0/advisories)
* [CVE-2017-11429](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-11429)
### Links:
* https://github.com/Clever/saml2/commit/3546cb61fd541f219abda364c5b919633609ef3d#diff-af730f9f738de1c9ad87596df3f6de84R279
* https://github.com/Clever/saml2/issues/127
* https://www.kb.cert.org/vuls/id/475445
DESC
end
it_behaves_like 'a created merge_request'
end
context 'when a description is NOT present' do
let(:params) do
{
category: 'dependency_scanning',
severity: "Unknown",
solution: "Upgrade to fixed version.",
file: "yarn.lock",
cve: "yarn.lock:saml2-js:gemnasium:9952e574-7b5b-46fa-a270-aeb694198a98",
title: 'Authentication bypass via incorrect DOM traversal and canonicalization in saml2-js',
tool: 'find_sec_bugs',
remediations: [
{ diff: diff }
]
}
end
let(:expected_title) { 'Resolve vulnerability: Authentication bypass via incorrect DOM traversal and canonicalization in saml2-js' }
let(:expected_description) do
<<~DESC.chomp
### Description:
Authentication bypass via incorrect DOM traversal and canonicalization in saml2-js
* Severity: Unknown
* Location: [yarn.lock](yarn.lock)
### Solution:
Upgrade to fixed version.
DESC
end
it_behaves_like 'a created merge_request'
end
end
end
context 'when params are invalid' do
context 'when category is unknown' do
let(:params) { { category: 'foo' } }
let(:result) { described_class.new(project, user, params).execute }
it 'return expected error' do
expect(result[:status]).to eq(:error)
expect(result[:message]).to eq('Invalid vulnerability category')
end
end
context 'when remediations are missing' do
let(:params) { { title: 'title', category: 'dependency_scanning', remediations: [] } }
let(:result) { described_class.new(project, user, params).execute }
it 'return expected error' do
expect(result[:status]).to eq(:error)
expect(result[:message]).to eq("Can't create merge request")
end
end
end
end
...@@ -2,7 +2,7 @@ require 'spec_helper' ...@@ -2,7 +2,7 @@ require 'spec_helper'
describe VulnerabilityFeedbackModule::CreateService, '#execute' do describe VulnerabilityFeedbackModule::CreateService, '#execute' do
let(:group) { create(:group) } let(:group) { create(:group) }
let(:project) { create(:project, :public, namespace: group) } let(:project) { create(:project, :public, :repository, namespace: group) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:pipeline) { create(:ci_pipeline, project: project) } let(:pipeline) { create(:ci_pipeline, project: project) }
...@@ -43,6 +43,8 @@ describe VulnerabilityFeedbackModule::CreateService, '#execute' do ...@@ -43,6 +43,8 @@ describe VulnerabilityFeedbackModule::CreateService, '#execute' do
expect(feedback.dismissal?).to eq(true) expect(feedback.dismissal?).to eq(true)
expect(feedback.issue?).to eq(false) expect(feedback.issue?).to eq(false)
expect(feedback.issue).to be_nil expect(feedback.issue).to be_nil
expect(feedback.merge_request?).to eq(false)
expect(feedback.merge_request).to be_nil
end end
end end
...@@ -68,6 +70,8 @@ describe VulnerabilityFeedbackModule::CreateService, '#execute' do ...@@ -68,6 +70,8 @@ describe VulnerabilityFeedbackModule::CreateService, '#execute' do
expect(feedback.dismissal?).to eq(false) expect(feedback.dismissal?).to eq(false)
expect(feedback.issue?).to eq(true) expect(feedback.issue?).to eq(true)
expect(feedback.issue).to be_an(Issue) expect(feedback.issue).to be_an(Issue)
expect(feedback.merge_request?).to eq(false)
expect(feedback.merge_request).to be_nil
end end
it 'delegates the Issue creation to CreateFromVulnerabilityDataService' do it 'delegates the Issue creation to CreateFromVulnerabilityDataService' do
...@@ -77,6 +81,82 @@ describe VulnerabilityFeedbackModule::CreateService, '#execute' do ...@@ -77,6 +81,82 @@ describe VulnerabilityFeedbackModule::CreateService, '#execute' do
expect(result[:status]).to eq(:success) expect(result[:status]).to eq(:success)
end end
end end
context 'when feedback_type is merge_request' do
let(:remediations_folder) { Rails.root.join('spec/fixtures/security-reports/remediations') }
let(:yarn_lock_content) do
File.read(
File.join(remediations_folder, "yarn.lock")
)
end
let(:project) do
create(:project, :custom_repo, namespace: group, files: { 'yarn.lock' => yarn_lock_content })
end
let(:remediation_diff) do
Base64.encode64(
File.read(
File.join(remediations_folder, "remediation.patch")
)
)
end
let(:result) do
params = feedback_params.merge(
feedback_type: 'merge_request',
category: 'dependency_scanning'
)
params[:vulnerability_data][:category] = 'dependency_scanning'
params[:vulnerability_data][:remediations] = [
{ diff: remediation_diff }
]
described_class.new(
project,
user,
params
).execute
end
it 'creates the feedback with the given params' do
expect(result[:status]).to eq(:success)
feedback = result[:vulnerability_feedback]
expect(feedback).to be_persisted
expect(feedback.project).to eq(project)
expect(feedback.author).to eq(user)
expect(feedback.feedback_type).to eq('merge_request')
expect(feedback.pipeline_id).to eq(pipeline.id)
expect(feedback.category).to eq('dependency_scanning')
expect(feedback.project_fingerprint).to eq('418291a26024a1445b23fe64de9380cdcdfd1fa8')
expect(feedback.dismissal?).to eq(false)
expect(feedback.issue?).to eq(false)
expect(feedback.issue).to be_nil
expect(feedback.merge_request?).to eq(true)
expect(feedback.merge_request).to be_an(MergeRequest)
end
it 'delegates the MergeRequest creation to CreateFromVulnerabilityDataService' do
expect_next_instance_of(MergeRequests::CreateFromVulnerabilityDataService) do |service|
expect(service).to receive(:execute).once.and_call_original
end
expect(result[:status]).to eq(:success)
end
it 'destroys merge_request and branch if feedback fails to persist' do
expect_next_instance_of(Vulnerabilities::Feedback) do |feedback|
expect(feedback).to receive(:save).and_return(false)
end
expect(result[:status]).to eq(:error)
expect(Vulnerabilities::Feedback.count).to eq 0
expect(MergeRequest.count).to eq 0
branches = BranchesFinder.new(project.repository, {}).execute
expect(branches.length).to eq 1
end
end
end end
context 'when params are invalid' do context 'when params are invalid' do
......
diff --git a/yarn.lock b/yarn.lock
index 0ecc92f..7fa4554 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2,103 +2,124 @@
# yarn lockfile v1
-async@~0.2.7:
- version "0.2.10"
- resolved "http://registry.npmjs.org/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1"
-
-async@~1.5.2:
- version "1.5.2"
- resolved "http://registry.npmjs.org/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
+async@^2.1.5, async@^2.5.0:
+ version "2.6.1"
+ resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610"
+ integrity sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==
+ dependencies:
+ lodash "^4.17.10"
-debug@^1.0.4:
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/debug/-/debug-1.0.5.tgz#f7241217430f99dec4c2b473eab92228e874c2ac"
+debug@^2.6.0:
+ version "2.6.9"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
+ integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
dependencies:
ms "2.0.0"
-ejs@~0.8.3:
- version "0.8.8"
- resolved "https://registry.yarnpkg.com/ejs/-/ejs-0.8.8.tgz#ffdc56dcc35d02926dd50ad13439bbc54061d598"
+ejs@^2.5.6:
+ version "2.6.1"
+ resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.6.1.tgz#498ec0d495655abc6f23cd61868d926464071aa0"
+ integrity sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ==
+
+lodash-node@~2.4.1:
+ version "2.4.1"
+ resolved "https://registry.yarnpkg.com/lodash-node/-/lodash-node-2.4.1.tgz#ea82f7b100c733d1a42af76801e506105e2a80ec"
+ integrity sha1-6oL3sQDHM9GkKvdoAeUGEF4qgOw=
+
+lodash@^4.17.10:
+ version "4.17.11"
+ resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
+ integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
+ integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
-node-forge@0.2.24:
- version "0.2.24"
- resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.2.24.tgz#fa6f846f42fa93f63a0a30c9fbff7b4e130e0858"
+node-forge@^0.7.0:
+ version "0.7.6"
+ resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.6.tgz#fdf3b418aee1f94f0ef642cd63486c77ca9724ac"
+ integrity sha512-sol30LUpz1jQFBjOKwbjxijiE3b6pjd74YwfD0fJOKPjF+fONKb2Yg8rYgS6+bK6VDl+/wfr4IYpC7jDzLUIfw==
saml2-js@^1.5.0:
- version "1.5.0"
- resolved "https://registry.yarnpkg.com/saml2-js/-/saml2-js-1.5.0.tgz#c0d2268a179e7329d29eb25aa82df5503774b0d9"
+ version "1.12.4"
+ resolved "https://registry.yarnpkg.com/saml2-js/-/saml2-js-1.12.4.tgz#c288f20bda6d2b91073b16c94ea72f22349ac3b3"
+ integrity sha1-wojyC9ptK5EHOxbJTqcvIjSaw7M=
dependencies:
- async "~1.5.2"
- debug "^1.0.4"
- underscore "~1.6.0"
- xml-crypto "^0.8.1"
- xml-encryption "~0.7.4"
- xml2js "~0.4.1"
- xmlbuilder "~2.1.0"
- xmldom "~0.1.19"
+ async "^2.5.0"
+ debug "^2.6.0"
+ underscore "^1.8.0"
+ xml-crypto "^0.10.0"
+ xml-encryption "^0.11.0"
+ xml2js "^0.4.0"
+ xmlbuilder "~2.2.0"
+ xmldom "^0.1.0"
sax@>=0.6.0:
version "1.2.4"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
+ integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
-underscore@>=1.5.x:
+underscore@^1.8.0:
version "1.9.1"
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.1.tgz#06dce34a0e68a7babc29b365b8e74b8925203961"
+ integrity sha512-5/4etnCkd9c8gwgowi5/om/mYO5ajCaOgdzj/oW+0eQV9WxKBDZw5+ycmKmeaTXjInS/W0BzpGLo2xR2aBwZdg==
-underscore@~1.6.0:
- version "1.6.0"
- resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.6.0.tgz#8b38b10cacdef63337b8b24e4ff86d45aea529a8"
-
-xml-crypto@^0.8.1:
- version "0.8.5"
- resolved "http://registry.npmjs.org/xml-crypto/-/xml-crypto-0.8.5.tgz#2bbcfb3eb33f3a82a218b822bf672b6b1c20e538"
+xml-crypto@^0.10.0:
+ version "0.10.1"
+ resolved "https://registry.yarnpkg.com/xml-crypto/-/xml-crypto-0.10.1.tgz#f832f74ccf56f24afcae1163a1fcab44d96774a8"
+ integrity sha1-+DL3TM9W8kr8rhFjofyrRNlndKg=
dependencies:
xmldom "=0.1.19"
xpath.js ">=0.0.3"
-xml-encryption@~0.7.4:
- version "0.7.4"
- resolved "https://registry.yarnpkg.com/xml-encryption/-/xml-encryption-0.7.4.tgz#42791ec64d556d2455dcb9da0a54123665ac65c7"
+xml-encryption@^0.11.0:
+ version "0.11.2"
+ resolved "https://registry.yarnpkg.com/xml-encryption/-/xml-encryption-0.11.2.tgz#c217f5509547e34b500b829f2c0bca85cca73a21"
+ integrity sha512-jVvES7i5ovdO7N+NjgncA326xYKjhqeAnnvIgRnY7ROLCfFqEDLwP0Sxp/30SHG0AXQV1048T5yinOFyvwGFzg==
dependencies:
- async "~0.2.7"
- ejs "~0.8.3"
- node-forge "0.2.24"
+ async "^2.1.5"
+ ejs "^2.5.6"
+ node-forge "^0.7.0"
xmldom "~0.1.15"
- xpath "0.0.5"
+ xpath "0.0.27"
-xml2js@~0.4.1:
+xml2js@^0.4.0:
version "0.4.19"
resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7"
+ integrity sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==
dependencies:
sax ">=0.6.0"
xmlbuilder "~9.0.1"
-xmlbuilder@~2.1.0:
- version "2.1.0"
- resolved "http://registry.npmjs.org/xmlbuilder/-/xmlbuilder-2.1.0.tgz#6ddae31683b6df12100b29fc8a0d4f46349abbed"
+xmlbuilder@~2.2.0:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-2.2.1.tgz#9326430f130d87435d4c4086643aa2926e105a32"
+ integrity sha1-kyZDDxMNh0NdTECGZDqikm4QWjI=
dependencies:
- underscore ">=1.5.x"
+ lodash-node "~2.4.1"
xmlbuilder@~9.0.1:
version "9.0.7"
- resolved "http://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d"
+ resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d"
+ integrity sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=
xmldom@=0.1.19:
version "0.1.19"
resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.19.tgz#631fc07776efd84118bf25171b37ed4d075a0abc"
+ integrity sha1-Yx/Ad3bv2EEYvyUXGzftTQdaCrw=
-xmldom@~0.1.15, xmldom@~0.1.19:
+xmldom@^0.1.0, xmldom@~0.1.15:
version "0.1.27"
resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.27.tgz#d501f97b3bdb403af8ef9ecc20573187aadac0e9"
+ integrity sha1-1QH5ezvbQDr4757MIFcxh6rawOk=
xpath.js@>=0.0.3:
version "1.1.0"
resolved "https://registry.yarnpkg.com/xpath.js/-/xpath.js-1.1.0.tgz#3816a44ed4bb352091083d002a383dd5104a5ff1"
+ integrity sha512-jg+qkfS4K8E7965sqaUl8mRngXiKb3WZGfONgE18pr03FUQiuSV6G+Ej4tS55B+rIQSFEIw3phdVAQ4pPqNWfQ==
-xpath@0.0.5:
- version "0.0.5"
- resolved "https://registry.yarnpkg.com/xpath/-/xpath-0.0.5.tgz#454036f6ef0f3df5af5d4ba4a119fb75674b3e6c"
+xpath@0.0.27:
+ version "0.0.27"
+ resolved "https://registry.yarnpkg.com/xpath/-/xpath-0.0.27.tgz#dd3421fbdcc5646ac32c48531b4d7e9d0c2cfa92"
+ integrity sha512-fg03WRxtkCV6ohClePNAECYsmpKKTv5L8y/X3Dn1hQrec3POx2jHZ/0P2qQ6HvsrU1BmeqXcof3NGGueG6LxwQ==
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
async@~0.2.7:
version "0.2.10"
resolved "http://registry.npmjs.org/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1"
async@~1.5.2:
version "1.5.2"
resolved "http://registry.npmjs.org/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a"
debug@^1.0.4:
version "1.0.5"
resolved "https://registry.yarnpkg.com/debug/-/debug-1.0.5.tgz#f7241217430f99dec4c2b473eab92228e874c2ac"
dependencies:
ms "2.0.0"
ejs@~0.8.3:
version "0.8.8"
resolved "https://registry.yarnpkg.com/ejs/-/ejs-0.8.8.tgz#ffdc56dcc35d02926dd50ad13439bbc54061d598"
ms@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
node-forge@0.2.24:
version "0.2.24"
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.2.24.tgz#fa6f846f42fa93f63a0a30c9fbff7b4e130e0858"
saml2-js@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/saml2-js/-/saml2-js-1.5.0.tgz#c0d2268a179e7329d29eb25aa82df5503774b0d9"
dependencies:
async "~1.5.2"
debug "^1.0.4"
underscore "~1.6.0"
xml-crypto "^0.8.1"
xml-encryption "~0.7.4"
xml2js "~0.4.1"
xmlbuilder "~2.1.0"
xmldom "~0.1.19"
sax@>=0.6.0:
version "1.2.4"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
underscore@>=1.5.x:
version "1.9.1"
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.1.tgz#06dce34a0e68a7babc29b365b8e74b8925203961"
underscore@~1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.6.0.tgz#8b38b10cacdef63337b8b24e4ff86d45aea529a8"
xml-crypto@^0.8.1:
version "0.8.5"
resolved "http://registry.npmjs.org/xml-crypto/-/xml-crypto-0.8.5.tgz#2bbcfb3eb33f3a82a218b822bf672b6b1c20e538"
dependencies:
xmldom "=0.1.19"
xpath.js ">=0.0.3"
xml-encryption@~0.7.4:
version "0.7.4"
resolved "https://registry.yarnpkg.com/xml-encryption/-/xml-encryption-0.7.4.tgz#42791ec64d556d2455dcb9da0a54123665ac65c7"
dependencies:
async "~0.2.7"
ejs "~0.8.3"
node-forge "0.2.24"
xmldom "~0.1.15"
xpath "0.0.5"
xml2js@~0.4.1:
version "0.4.19"
resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.19.tgz#686c20f213209e94abf0d1bcf1efaa291c7827a7"
dependencies:
sax ">=0.6.0"
xmlbuilder "~9.0.1"
xmlbuilder@~2.1.0:
version "2.1.0"
resolved "http://registry.npmjs.org/xmlbuilder/-/xmlbuilder-2.1.0.tgz#6ddae31683b6df12100b29fc8a0d4f46349abbed"
dependencies:
underscore ">=1.5.x"
xmlbuilder@~9.0.1:
version "9.0.7"
resolved "http://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz#132ee63d2ec5565c557e20f4c22df9aca686b10d"
xmldom@=0.1.19:
version "0.1.19"
resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.19.tgz#631fc07776efd84118bf25171b37ed4d075a0abc"
xmldom@~0.1.15, xmldom@~0.1.19:
version "0.1.27"
resolved "https://registry.yarnpkg.com/xmldom/-/xmldom-0.1.27.tgz#d501f97b3bdb403af8ef9ecc20573187aadac0e9"
xpath.js@>=0.0.3:
version "1.1.0"
resolved "https://registry.yarnpkg.com/xpath.js/-/xpath.js-1.1.0.tgz#3816a44ed4bb352091083d002a383dd5104a5ff1"
xpath@0.0.5:
version "0.0.5"
resolved "https://registry.yarnpkg.com/xpath/-/xpath-0.0.5.tgz#454036f6ef0f3df5af5d4ba4a119fb75674b3e6c"
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