From f16377e7dc762462817dd0b34e36811c55988b10 Mon Sep 17 00:00:00 2001 From: James Edwards-Jones Date: Wed, 5 Apr 2017 18:59:46 +0100 Subject: [PATCH] Protected Tags backend review changes Added changelog --- .../projects/protected_branches_controller.rb | 18 +++---------- .../projects/protected_refs_controller.rb | 21 +++++++-------- .../projects/protected_tags_controller.rb | 18 +++---------- app/helpers/branches_helper.rb | 4 +++ .../concerns/protected_branch_access.rb | 1 + app/models/concerns/protected_ref.rb | 1 + app/models/concerns/protected_tag_access.rb | 1 + app/models/project.rb | 2 +- app/models/protectable_dropdown.rb | 11 ++++++-- .../protected_branches/update_service.rb | 7 ++--- app/services/protected_tags/update_service.rb | 7 ++--- app/views/projects/branches/_branch.html.haml | 2 +- .../protected_branches/show.html.haml | 8 +++--- .../projects/protected_tags/show.html.haml | 8 +++--- ...471-restrict-tag-pushes-protected-tags.yml | 4 +++ .../20170309173138_create_protected_tags.rb | 1 - lib/gitlab/checks/change_access.rb | 10 ++----- .../protected_branches_controller_spec.rb | 1 + .../protected_tags_controller_spec.rb | 1 + .../access_control_ce_spec.rb | 12 +++++++++ .../protected_tags/access_control_ce_spec.rb | 6 +++++ spec/models/protectable_dropdown_spec.rb | 1 + spec/models/protected_tag_spec.rb | 2 -- .../protected_branches/update_service_spec.rb | 26 +++++++++++++++++++ .../protected_tags/update_service_spec.rb | 26 +++++++++++++++++++ 25 files changed, 125 insertions(+), 74 deletions(-) create mode 100644 changelogs/unreleased/18471-restrict-tag-pushes-protected-tags.yml create mode 100644 spec/services/protected_branches/update_service_spec.rb create mode 100644 spec/services/protected_tags/update_service_spec.rb diff --git a/app/controllers/projects/protected_branches_controller.rb b/app/controllers/projects/protected_branches_controller.rb index c2a55c9500a..ba24fa9acfe 100644 --- a/app/controllers/projects/protected_branches_controller.rb +++ b/app/controllers/projects/protected_branches_controller.rb @@ -1,32 +1,20 @@ class Projects::ProtectedBranchesController < Projects::ProtectedRefsController protected - def protected_ref - @protected_branch - end - - def protected_ref=(val) - @protected_branch = val - end - - def matching_refs=(val) - @matching_branches = val - end - def project_refs @project.repository.branches end - def create_service + def create_service_class ::ProtectedBranches::CreateService end - def update_service + def update_service_class ::ProtectedBranches::UpdateService end def load_protected_ref - self.protected_ref = @project.protected_branches.find(params[:id]) + @protected_ref = @project.protected_branches.find(params[:id]) end def protected_ref_params diff --git a/app/controllers/projects/protected_refs_controller.rb b/app/controllers/projects/protected_refs_controller.rb index 63f005124a9..083a70968e5 100644 --- a/app/controllers/projects/protected_refs_controller.rb +++ b/app/controllers/projects/protected_refs_controller.rb @@ -1,5 +1,6 @@ class Projects::ProtectedRefsController < Projects::ApplicationController include RepositorySettingsRedirect + # Authorize before_action :require_non_empty_project before_action :authorize_admin_project! @@ -12,33 +13,31 @@ class Projects::ProtectedRefsController < Projects::ApplicationController end def create - self.protected_ref = create_service.new(@project, current_user, protected_ref_params).execute + protected_ref = create_service_class.new(@project, current_user, protected_ref_params).execute + unless protected_ref.persisted? flash[:alert] = protected_ref.errors.full_messages.join(', ').html_safe end + redirect_to_repository_settings(@project) end def show - self.matching_refs = protected_ref.matching(project_refs) + @matching_refs = @protected_ref.matching(project_refs) end def update - self.protected_ref = update_service.new(@project, current_user, protected_ref_params).execute(protected_ref) + @protected_ref = update_service_class.new(@project, current_user, protected_ref_params).execute(@protected_ref) - if protected_ref.valid? - respond_to do |format| - format.json { render json: protected_ref, status: :ok } - end + if @protected_ref.valid? + render json: @protected_ref, status: :ok else - respond_to do |format| - format.json { render json: protected_ref.errors, status: :unprocessable_entity } - end + render json: @protected_ref.errors, status: :unprocessable_entity end end def destroy - protected_ref.destroy + @protected_ref.destroy respond_to do |format| format.html { redirect_to_repository_settings(@project) } diff --git a/app/controllers/projects/protected_tags_controller.rb b/app/controllers/projects/protected_tags_controller.rb index 0e00baedbdf..c61ddf145e6 100644 --- a/app/controllers/projects/protected_tags_controller.rb +++ b/app/controllers/projects/protected_tags_controller.rb @@ -1,32 +1,20 @@ class Projects::ProtectedTagsController < Projects::ProtectedRefsController protected - def protected_ref - @protected_tag - end - - def protected_ref=(val) - @protected_tag = val - end - - def matching_refs=(val) - @matching_tags = val - end - def project_refs @project.repository.tags end - def create_service + def create_service_class ::ProtectedTags::CreateService end - def update_service + def update_service_class ::ProtectedTags::UpdateService end def load_protected_ref - self.protected_ref = @project.protected_tags.find(params[:id]) + @protected_ref = @project.protected_tags.find(params[:id]) end def protected_ref_params diff --git a/app/helpers/branches_helper.rb b/app/helpers/branches_helper.rb index a852b90c57e..b7a28b1b4a7 100644 --- a/app/helpers/branches_helper.rb +++ b/app/helpers/branches_helper.rb @@ -29,4 +29,8 @@ module BranchesHelper def project_branches options_for_select(@project.repository.branch_names, @project.default_branch) end + + def protected_branch?(project, branch) + ProtectedBranch.protected?(project, branch.name) + end end diff --git a/app/models/concerns/protected_branch_access.rb b/app/models/concerns/protected_branch_access.rb index 06cae00249a..c41b807df8a 100644 --- a/app/models/concerns/protected_branch_access.rb +++ b/app/models/concerns/protected_branch_access.rb @@ -5,6 +5,7 @@ module ProtectedBranchAccess include ProtectedRefAccess belongs_to :protected_branch + delegate :project, to: :protected_branch end end diff --git a/app/models/concerns/protected_ref.rb b/app/models/concerns/protected_ref.rb index 7c0183952a0..62eaec2407f 100644 --- a/app/models/concerns/protected_ref.rb +++ b/app/models/concerns/protected_ref.rb @@ -3,6 +3,7 @@ module ProtectedRef included do belongs_to :project + validates :name, presence: true validates :project, presence: true diff --git a/app/models/concerns/protected_tag_access.rb b/app/models/concerns/protected_tag_access.rb index 9b7d31a6fd5..ee65de24dd8 100644 --- a/app/models/concerns/protected_tag_access.rb +++ b/app/models/concerns/protected_tag_access.rb @@ -5,6 +5,7 @@ module ProtectedTagAccess include ProtectedRefAccess belongs_to :protected_tag + delegate :project, to: :protected_tag end end diff --git a/app/models/project.rb b/app/models/project.rb index 2469e6f8523..2c631050042 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -134,7 +134,7 @@ class Project < ActiveRecord::Base has_many :snippets, dependent: :destroy, class_name: 'ProjectSnippet' has_many :hooks, dependent: :destroy, class_name: 'ProjectHook' has_many :protected_branches, dependent: :destroy - has_many :protected_tags, dependent: :destroy + has_many :protected_tags, dependent: :destroy has_many :project_authorizations has_many :authorized_users, through: :project_authorizations, source: :user, class_name: 'User' diff --git a/app/models/protectable_dropdown.rb b/app/models/protectable_dropdown.rb index c9b2b213cd2..122fbce257d 100644 --- a/app/models/protectable_dropdown.rb +++ b/app/models/protectable_dropdown.rb @@ -6,8 +6,7 @@ class ProtectableDropdown # Tags/branches which are yet to be individually protected def protectable_ref_names - non_wildcard_protections = protections.reject(&:wildcard?) - refs.map(&:name) - non_wildcard_protections.map(&:name) + @protectable_ref_names ||= ref_names - non_wildcard_protected_ref_names end def hash @@ -20,7 +19,15 @@ class ProtectableDropdown @project.repository.public_send(@ref_type) end + def ref_names + refs.map(&:name) + end + def protections @project.public_send("protected_#{@ref_type}") end + + def non_wildcard_protected_ref_names + protections.reject(&:wildcard?).map(&:name) + end end diff --git a/app/services/protected_branches/update_service.rb b/app/services/protected_branches/update_service.rb index 89d8ba60134..4b3337a5c9d 100644 --- a/app/services/protected_branches/update_service.rb +++ b/app/services/protected_branches/update_service.rb @@ -1,13 +1,10 @@ module ProtectedBranches class UpdateService < BaseService - attr_reader :protected_branch - def execute(protected_branch) raise Gitlab::Access::AccessDeniedError unless can?(current_user, :admin_project, project) - @protected_branch = protected_branch - @protected_branch.update(params) - @protected_branch + protected_branch.update(params) + protected_branch end end end diff --git a/app/services/protected_tags/update_service.rb b/app/services/protected_tags/update_service.rb index 8a2419efd7b..aea6a48968d 100644 --- a/app/services/protected_tags/update_service.rb +++ b/app/services/protected_tags/update_service.rb @@ -1,13 +1,10 @@ module ProtectedTags class UpdateService < BaseService - attr_reader :protected_tag - def execute(protected_tag) raise Gitlab::Access::AccessDeniedError unless can?(current_user, :admin_project, project) - @protected_tag = protected_tag - @protected_tag.update(params) - @protected_tag + protected_tag.update(params) + protected_tag end end end diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml index d84fa9e55c0..931a5f920d3 100644 --- a/app/views/projects/branches/_branch.html.haml +++ b/app/views/projects/branches/_branch.html.haml @@ -15,7 +15,7 @@ %span.label.label-info.has-tooltip{ title: "Merged into #{@repository.root_ref}" } merged - - if ProtectedBranch.protected?(@project, branch.name) + - if protected_branch?(@project, branch) %span.label.label-success protected .controls.hidden-xs< diff --git a/app/views/projects/protected_branches/show.html.haml b/app/views/projects/protected_branches/show.html.haml index 4d8169815b3..f8cfe5e4b11 100644 --- a/app/views/projects/protected_branches/show.html.haml +++ b/app/views/projects/protected_branches/show.html.haml @@ -1,13 +1,13 @@ -- page_title @protected_branch.name, "Protected Branches" +- page_title @protected_ref.name, "Protected Branches" .row.prepend-top-default.append-bottom-default .col-lg-3 %h4.prepend-top-0 - = @protected_branch.name + = @protected_ref.name .col-lg-9 %h5 Matching Branches - - if @matching_branches.present? + - if @matching_refs.present? .table-responsive %table.table.protected-branches-list %colgroup @@ -18,7 +18,7 @@ %th Branch %th Last commit %tbody - - @matching_branches.each do |matching_branch| + - @matching_refs.each do |matching_branch| = render partial: "matching_branch", object: matching_branch - else %p.settings-message.text-center diff --git a/app/views/projects/protected_tags/show.html.haml b/app/views/projects/protected_tags/show.html.haml index 185807a7e8d..63743f28b3c 100644 --- a/app/views/projects/protected_tags/show.html.haml +++ b/app/views/projects/protected_tags/show.html.haml @@ -1,13 +1,13 @@ -- page_title @protected_tag.name, "Protected Tags" +- page_title @protected_ref.name, "Protected Tags" .row.prepend-top-default.append-bottom-default .col-lg-3 %h4.prepend-top-0 - = @protected_tag.name + = @protected_ref.name .col-lg-9 %h5 Matching Tags - - if @matching_tags.present? + - if @matching_refs.present? .table-responsive %table.table.protected-tags-list %colgroup @@ -18,7 +18,7 @@ %th Tag %th Last commit %tbody - - @matching_tags.each do |matching_tag| + - @matching_refs.each do |matching_tag| = render partial: "matching_tag", object: matching_tag - else %p.settings-message.text-center diff --git a/changelogs/unreleased/18471-restrict-tag-pushes-protected-tags.yml b/changelogs/unreleased/18471-restrict-tag-pushes-protected-tags.yml new file mode 100644 index 00000000000..c6ea5da65a5 --- /dev/null +++ b/changelogs/unreleased/18471-restrict-tag-pushes-protected-tags.yml @@ -0,0 +1,4 @@ +--- +title: Protected Tags feature +merge_request: 10356 +author: diff --git a/db/migrate/20170309173138_create_protected_tags.rb b/db/migrate/20170309173138_create_protected_tags.rb index 538f28479c7..796f3c90344 100644 --- a/db/migrate/20170309173138_create_protected_tags.rb +++ b/db/migrate/20170309173138_create_protected_tags.rb @@ -1,7 +1,6 @@ class CreateProtectedTags < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers - # Set this constant to true if this migration requires downtime. DOWNTIME = false GITLAB_ACCESS_MASTER = 40 diff --git a/lib/gitlab/checks/change_access.rb b/lib/gitlab/checks/change_access.rb index 0d96c4d41d7..eb2f2e144fd 100644 --- a/lib/gitlab/checks/change_access.rb +++ b/lib/gitlab/checks/change_access.rb @@ -70,14 +70,8 @@ module Gitlab def protected_tag_checks return unless tag_protected? - - if update? - return "Protected tags cannot be updated." - end - - if deletion? - return "Protected tags cannot be deleted." - end + return "Protected tags cannot be updated." if update? + return "Protected tags cannot be deleted." if deletion? unless user_access.can_create_tag?(@tag_name) return "You are not allowed to create this tag as it is protected." diff --git a/spec/controllers/projects/protected_branches_controller_spec.rb b/spec/controllers/projects/protected_branches_controller_spec.rb index e378b5714fe..80be135b5d8 100644 --- a/spec/controllers/projects/protected_branches_controller_spec.rb +++ b/spec/controllers/projects/protected_branches_controller_spec.rb @@ -3,6 +3,7 @@ require('spec_helper') describe Projects::ProtectedBranchesController do describe "GET #index" do let(:project) { create(:project_empty_repo, :public) } + it "redirects empty repo to projects page" do get(:index, namespace_id: project.namespace.to_param, project_id: project) end diff --git a/spec/controllers/projects/protected_tags_controller_spec.rb b/spec/controllers/projects/protected_tags_controller_spec.rb index ac802981294..64658988b3f 100644 --- a/spec/controllers/projects/protected_tags_controller_spec.rb +++ b/spec/controllers/projects/protected_tags_controller_spec.rb @@ -3,6 +3,7 @@ require('spec_helper') describe Projects::ProtectedTagsController do describe "GET #index" do let(:project) { create(:project_empty_repo, :public) } + it "redirects empty repo to projects page" do get(:index, namespace_id: project.namespace.to_param, project_id: project) end diff --git a/spec/features/protected_branches/access_control_ce_spec.rb b/spec/features/protected_branches/access_control_ce_spec.rb index e4aca25a339..eb3cea775da 100644 --- a/spec/features/protected_branches/access_control_ce_spec.rb +++ b/spec/features/protected_branches/access_control_ce_spec.rb @@ -2,7 +2,9 @@ RSpec.shared_examples "protected branches > access control > CE" do ProtectedBranch::PushAccessLevel.human_access_levels.each do |(access_type_id, access_type_name)| it "allows creating protected branches that #{access_type_name} can push to" do visit namespace_project_protected_branches_path(project.namespace, project) + set_protected_branch_name('master') + within('.new_protected_branch') do allowed_to_push_button = find(".js-allowed-to-push") @@ -11,6 +13,7 @@ RSpec.shared_examples "protected branches > access control > CE" do within(".dropdown.open .dropdown-menu") { click_on access_type_name } end end + click_on "Protect" expect(ProtectedBranch.count).to eq(1) @@ -19,7 +22,9 @@ RSpec.shared_examples "protected branches > access control > CE" do it "allows updating protected branches so that #{access_type_name} can push to them" do visit namespace_project_protected_branches_path(project.namespace, project) + set_protected_branch_name('master') + click_on "Protect" expect(ProtectedBranch.count).to eq(1) @@ -34,6 +39,7 @@ RSpec.shared_examples "protected branches > access control > CE" do end wait_for_ajax + expect(ProtectedBranch.last.push_access_levels.map(&:access_level)).to include(access_type_id) end end @@ -41,7 +47,9 @@ RSpec.shared_examples "protected branches > access control > CE" do ProtectedBranch::MergeAccessLevel.human_access_levels.each do |(access_type_id, access_type_name)| it "allows creating protected branches that #{access_type_name} can merge to" do visit namespace_project_protected_branches_path(project.namespace, project) + set_protected_branch_name('master') + within('.new_protected_branch') do allowed_to_merge_button = find(".js-allowed-to-merge") @@ -50,6 +58,7 @@ RSpec.shared_examples "protected branches > access control > CE" do within(".dropdown.open .dropdown-menu") { click_on access_type_name } end end + click_on "Protect" expect(ProtectedBranch.count).to eq(1) @@ -58,7 +67,9 @@ RSpec.shared_examples "protected branches > access control > CE" do it "allows updating protected branches so that #{access_type_name} can merge to them" do visit namespace_project_protected_branches_path(project.namespace, project) + set_protected_branch_name('master') + click_on "Protect" expect(ProtectedBranch.count).to eq(1) @@ -73,6 +84,7 @@ RSpec.shared_examples "protected branches > access control > CE" do end wait_for_ajax + expect(ProtectedBranch.last.merge_access_levels.map(&:access_level)).to include(access_type_id) end end diff --git a/spec/features/protected_tags/access_control_ce_spec.rb b/spec/features/protected_tags/access_control_ce_spec.rb index 33a07786007..de2556041e7 100644 --- a/spec/features/protected_tags/access_control_ce_spec.rb +++ b/spec/features/protected_tags/access_control_ce_spec.rb @@ -2,7 +2,9 @@ RSpec.shared_examples "protected tags > access control > CE" do ProtectedTag::CreateAccessLevel.human_access_levels.each do |(access_type_id, access_type_name)| it "allows creating protected tags that #{access_type_name} can create" do visit namespace_project_protected_tags_path(project.namespace, project) + set_protected_tag_name('master') + within('.new_protected_tag') do allowed_to_create_button = find(".js-allowed-to-create") @@ -11,6 +13,7 @@ RSpec.shared_examples "protected tags > access control > CE" do within(".dropdown.open .dropdown-menu") { click_on access_type_name } end end + click_on "Protect" expect(ProtectedTag.count).to eq(1) @@ -19,7 +22,9 @@ RSpec.shared_examples "protected tags > access control > CE" do it "allows updating protected tags so that #{access_type_name} can create them" do visit namespace_project_protected_tags_path(project.namespace, project) + set_protected_tag_name('master') + click_on "Protect" expect(ProtectedTag.count).to eq(1) @@ -34,6 +39,7 @@ RSpec.shared_examples "protected tags > access control > CE" do end wait_for_ajax + expect(ProtectedTag.last.create_access_levels.map(&:access_level)).to include(access_type_id) end end diff --git a/spec/models/protectable_dropdown_spec.rb b/spec/models/protectable_dropdown_spec.rb index 7f8ef7195e5..4c9bade592b 100644 --- a/spec/models/protectable_dropdown_spec.rb +++ b/spec/models/protectable_dropdown_spec.rb @@ -18,6 +18,7 @@ describe ProtectableDropdown, models: true do create(:protected_branch, name: 'feat*', project: project) subject = described_class.new(project.reload, :branches) + expect(subject.protectable_ref_names).to include('feature') end end diff --git a/spec/models/protected_tag_spec.rb b/spec/models/protected_tag_spec.rb index 05ad532935a..51353852a93 100644 --- a/spec/models/protected_tag_spec.rb +++ b/spec/models/protected_tag_spec.rb @@ -1,8 +1,6 @@ require 'spec_helper' describe ProtectedTag, models: true do - subject { build_stubbed(:protected_branch) } - describe 'Associations' do it { is_expected.to belong_to(:project) } end diff --git a/spec/services/protected_branches/update_service_spec.rb b/spec/services/protected_branches/update_service_spec.rb new file mode 100644 index 00000000000..62bdd49a4d7 --- /dev/null +++ b/spec/services/protected_branches/update_service_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +describe ProtectedBranches::UpdateService, services: true do + let(:protected_branch) { create(:protected_branch) } + let(:project) { protected_branch.project } + let(:user) { project.owner } + let(:params) { { name: 'new protected branch name' } } + + describe '#execute' do + subject(:service) { described_class.new(project, user, params) } + + it 'updates a protected branch' do + result = service.execute(protected_branch) + + expect(result.reload.name).to eq(params[:name]) + end + + context 'without admin_project permissions' do + let(:user) { create(:user) } + + it "raises error" do + expect{ service.execute(protected_branch) }.to raise_error(Gitlab::Access::AccessDeniedError) + end + end + end +end diff --git a/spec/services/protected_tags/update_service_spec.rb b/spec/services/protected_tags/update_service_spec.rb new file mode 100644 index 00000000000..e78fde4c48d --- /dev/null +++ b/spec/services/protected_tags/update_service_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +describe ProtectedTags::UpdateService, services: true do + let(:protected_tag) { create(:protected_tag) } + let(:project) { protected_tag.project } + let(:user) { project.owner } + let(:params) { { name: 'new protected tag name' } } + + describe '#execute' do + subject(:service) { described_class.new(project, user, params) } + + it 'updates a protected tag' do + result = service.execute(protected_tag) + + expect(result.reload.name).to eq(params[:name]) + end + + context 'without admin_project permissions' do + let(:user) { create(:user) } + + it "raises error" do + expect{ service.execute(protected_tag) }.to raise_error(Gitlab::Access::AccessDeniedError) + end + end + end +end