Commit 1e198653 authored by GitLab Release Tools Bot's avatar GitLab Release Tools Bot

Merge branch 'security-protected-branch-name-escaped-fix' into 'master'

Unescape and sanitize protected branch name on create and update

See merge request gitlab-org/security/gitlab!1953
parents b7dcacfd d195dd1c
...@@ -13,5 +13,23 @@ module ProtectedBranches ...@@ -13,5 +13,23 @@ module ProtectedBranches
def after_execute(*) def after_execute(*)
# overridden in EE::ProtectedBranches module # overridden in EE::ProtectedBranches module
end end
def filtered_params
return unless params
params[:name] = sanitize_branch_name(params[:name]) if params[:name].present?
params
end
private
def sanitize_branch_name(name)
name = CGI.unescapeHTML(name)
name = Sanitize.fragment(name)
# Sanitize.fragment escapes HTML chars, so unescape again to allow names
# like `feature->master`
CGI.unescapeHTML(name)
end
end end
end end
...@@ -21,7 +21,7 @@ module ProtectedBranches ...@@ -21,7 +21,7 @@ module ProtectedBranches
end end
def protected_branch def protected_branch
@protected_branch ||= project.protected_branches.new(params) @protected_branch ||= project.protected_branches.new(filtered_params)
end end
end end
end end
......
...@@ -8,7 +8,7 @@ module ProtectedBranches ...@@ -8,7 +8,7 @@ module ProtectedBranches
old_merge_access_levels = protected_branch.merge_access_levels.map(&:clone) old_merge_access_levels = protected_branch.merge_access_levels.map(&:clone)
old_push_access_levels = protected_branch.push_access_levels.map(&:clone) old_push_access_levels = protected_branch.push_access_levels.map(&:clone)
if protected_branch.update(params) if protected_branch.update(filtered_params)
after_execute(protected_branch: protected_branch, old_merge_access_levels: old_merge_access_levels, old_push_access_levels: old_push_access_levels) after_execute(protected_branch: protected_branch, old_merge_access_levels: old_merge_access_levels, old_push_access_levels: old_push_access_levels)
end end
......
...@@ -118,12 +118,12 @@ RSpec.describe 'Protected Branches', :js do ...@@ -118,12 +118,12 @@ RSpec.describe 'Protected Branches', :js do
it "allows creating explicit protected branches" do it "allows creating explicit protected branches" do
visit project_protected_branches_path(project) visit project_protected_branches_path(project)
set_defaults set_defaults
set_protected_branch_name('some-branch') set_protected_branch_name('some->branch')
click_on "Protect" click_on "Protect"
within(".protected-branches-list") { expect(page).to have_content('some-branch') } within(".protected-branches-list") { expect(page).to have_content('some->branch') }
expect(ProtectedBranch.count).to eq(1) expect(ProtectedBranch.count).to eq(1)
expect(ProtectedBranch.last.name).to eq('some-branch') expect(ProtectedBranch.last.name).to eq('some->branch')
end end
it "displays the last commit on the matching branch if it exists" do it "displays the last commit on the matching branch if it exists" do
......
...@@ -7,13 +7,15 @@ RSpec.describe ProtectedBranches::CreateService do ...@@ -7,13 +7,15 @@ RSpec.describe ProtectedBranches::CreateService do
let(:user) { project.owner } let(:user) { project.owner }
let(:params) do let(:params) do
{ {
name: 'master', name: name,
merge_access_levels_attributes: [{ access_level: Gitlab::Access::MAINTAINER }], merge_access_levels_attributes: [{ access_level: Gitlab::Access::MAINTAINER }],
push_access_levels_attributes: [{ access_level: Gitlab::Access::MAINTAINER }] push_access_levels_attributes: [{ access_level: Gitlab::Access::MAINTAINER }]
} }
end end
describe '#execute' do describe '#execute' do
let(:name) { 'master' }
subject(:service) { described_class.new(project, user, params) } subject(:service) { described_class.new(project, user, params) }
it 'creates a new protected branch' do it 'creates a new protected branch' do
...@@ -22,6 +24,41 @@ RSpec.describe ProtectedBranches::CreateService do ...@@ -22,6 +24,41 @@ RSpec.describe ProtectedBranches::CreateService do
expect(project.protected_branches.last.merge_access_levels.map(&:access_level)).to eq([Gitlab::Access::MAINTAINER]) expect(project.protected_branches.last.merge_access_levels.map(&:access_level)).to eq([Gitlab::Access::MAINTAINER])
end end
context 'when name has escaped HTML' do
let(:name) { 'feature->test' }
it 'creates the new protected branch matching the unescaped version' do
expect { service.execute }.to change(ProtectedBranch, :count).by(1)
expect(project.protected_branches.last.name).to eq('feature->test')
end
context 'and name contains HTML tags' do
let(:name) { '<b>master</b>' }
it 'creates the new protected branch with sanitized name' do
expect { service.execute }.to change(ProtectedBranch, :count).by(1)
expect(project.protected_branches.last.name).to eq('master')
end
context 'and contains unsafe HTML' do
let(:name) { '<script>alert('foo');</script>' }
it 'does not create the new protected branch' do
expect { service.execute }.not_to change(ProtectedBranch, :count)
end
end
end
context 'when name contains unescaped HTML tags' do
let(:name) { '<b>master</b>' }
it 'creates the new protected branch with sanitized name' do
expect { service.execute }.to change(ProtectedBranch, :count).by(1)
expect(project.protected_branches.last.name).to eq('master')
end
end
end
context 'when user does not have permission' do context 'when user does not have permission' do
let(:user) { create(:user) } let(:user) { create(:user) }
......
...@@ -6,17 +6,50 @@ RSpec.describe ProtectedBranches::UpdateService do ...@@ -6,17 +6,50 @@ RSpec.describe ProtectedBranches::UpdateService do
let(:protected_branch) { create(:protected_branch) } let(:protected_branch) { create(:protected_branch) }
let(:project) { protected_branch.project } let(:project) { protected_branch.project }
let(:user) { project.owner } let(:user) { project.owner }
let(:params) { { name: 'new protected branch name' } } let(:params) { { name: new_name } }
describe '#execute' do describe '#execute' do
let(:new_name) { 'new protected branch name' }
let(:result) { service.execute(protected_branch) }
subject(:service) { described_class.new(project, user, params) } subject(:service) { described_class.new(project, user, params) }
it 'updates a protected branch' do it 'updates a protected branch' do
result = service.execute(protected_branch)
expect(result.reload.name).to eq(params[:name]) expect(result.reload.name).to eq(params[:name])
end end
context 'when name has escaped HTML' do
let(:new_name) { 'feature-&gt;test' }
it 'updates protected branch name with unescaped HTML' do
expect(result.reload.name).to eq('feature->test')
end
context 'and name contains HTML tags' do
let(:new_name) { '&lt;b&gt;master&lt;/b&gt;' }
it 'updates protected branch name with sanitized name' do
expect(result.reload.name).to eq('master')
end
context 'and contains unsafe HTML' do
let(:new_name) { '&lt;script&gt;alert(&#39;foo&#39;);&lt;/script&gt;' }
it 'does not update the protected branch' do
expect(result.reload.name).to eq(protected_branch.name)
end
end
end
end
context 'when name contains unescaped HTML tags' do
let(:new_name) { '<b>master</b>' }
it 'updates protected branch name with sanitized name' do
expect(result.reload.name).to eq('master')
end
end
context 'without admin_project permissions' do context 'without admin_project permissions' do
let(:user) { create(:user) } let(:user) { create(:user) }
......
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