Merge branch 'jej/add-protected-branch-policy' into 'master'
Add protected branch policy See merge request gitlab-org/gitlab-ce!17982
This commit is contained in:
		
						commit
						96b355dca0
					
				| 
						 | 
				
			
			@ -5,12 +5,8 @@ class Projects::ProtectedBranchesController < Projects::ProtectedRefsController
 | 
			
		|||
    @project.repository.branches
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def create_service_class
 | 
			
		||||
    ::ProtectedBranches::CreateService
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def update_service_class
 | 
			
		||||
    ::ProtectedBranches::UpdateService
 | 
			
		||||
  def service_namespace
 | 
			
		||||
    ::ProtectedBranches
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def load_protected_ref
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -37,7 +37,7 @@ class Projects::ProtectedRefsController < Projects::ApplicationController
 | 
			
		|||
  end
 | 
			
		||||
 | 
			
		||||
  def destroy
 | 
			
		||||
    @protected_ref.destroy
 | 
			
		||||
    destroy_service_class.new(@project, current_user).execute(@protected_ref)
 | 
			
		||||
 | 
			
		||||
    respond_to do |format|
 | 
			
		||||
      format.html { redirect_to_repository_settings(@project) }
 | 
			
		||||
| 
						 | 
				
			
			@ -47,6 +47,18 @@ class Projects::ProtectedRefsController < Projects::ApplicationController
 | 
			
		|||
 | 
			
		||||
  protected
 | 
			
		||||
 | 
			
		||||
  def create_service_class
 | 
			
		||||
    service_namespace::CreateService
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def update_service_class
 | 
			
		||||
    service_namespace::UpdateService
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def destroy_service_class
 | 
			
		||||
    service_namespace::DestroyService
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def access_level_attributes
 | 
			
		||||
    %i(access_level id)
 | 
			
		||||
  end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,12 +5,8 @@ class Projects::ProtectedTagsController < Projects::ProtectedRefsController
 | 
			
		|||
    @project.repository.tags
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def create_service_class
 | 
			
		||||
    ::ProtectedTags::CreateService
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def update_service_class
 | 
			
		||||
    ::ProtectedTags::UpdateService
 | 
			
		||||
  def service_namespace
 | 
			
		||||
    ::ProtectedTags
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  def load_protected_ref
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,9 @@
 | 
			
		|||
class ProtectedBranchPolicy < BasePolicy
 | 
			
		||||
  delegate { @subject.project }
 | 
			
		||||
 | 
			
		||||
  rule { can?(:admin_project) }.policy do
 | 
			
		||||
    enable :create_protected_branch
 | 
			
		||||
    enable :update_protected_branch
 | 
			
		||||
    enable :destroy_protected_branch
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -1,11 +1,20 @@
 | 
			
		|||
module ProtectedBranches
 | 
			
		||||
  class CreateService < BaseService
 | 
			
		||||
    attr_reader :protected_branch
 | 
			
		||||
 | 
			
		||||
    def execute(skip_authorization: false)
 | 
			
		||||
      raise Gitlab::Access::AccessDeniedError unless skip_authorization || can?(current_user, :admin_project, project)
 | 
			
		||||
      raise Gitlab::Access::AccessDeniedError unless skip_authorization || authorized?
 | 
			
		||||
 | 
			
		||||
      project.protected_branches.create(params)
 | 
			
		||||
      protected_branch.save
 | 
			
		||||
      protected_branch
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def authorized?
 | 
			
		||||
      can?(current_user, :create_protected_branch, protected_branch)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    private
 | 
			
		||||
 | 
			
		||||
    def protected_branch
 | 
			
		||||
      @protected_branch ||= project.protected_branches.new(params)
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,9 @@
 | 
			
		|||
module ProtectedBranches
 | 
			
		||||
  class DestroyService < BaseService
 | 
			
		||||
    def execute(protected_branch)
 | 
			
		||||
      raise Gitlab::Access::AccessDeniedError unless can?(current_user, :destroy_protected_branch, protected_branch)
 | 
			
		||||
 | 
			
		||||
      protected_branch.destroy
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -1,7 +1,7 @@
 | 
			
		|||
module ProtectedBranches
 | 
			
		||||
  class UpdateService < BaseService
 | 
			
		||||
    def execute(protected_branch)
 | 
			
		||||
      raise Gitlab::Access::AccessDeniedError unless can?(current_user, :admin_project, project)
 | 
			
		||||
      raise Gitlab::Access::AccessDeniedError unless can?(current_user, :update_protected_branch, protected_branch)
 | 
			
		||||
 | 
			
		||||
      protected_branch.update(params)
 | 
			
		||||
      protected_branch
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,7 @@
 | 
			
		|||
module ProtectedTags
 | 
			
		||||
  class DestroyService < BaseService
 | 
			
		||||
    def execute(protected_tag)
 | 
			
		||||
      protected_tag.destroy
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -70,7 +70,10 @@ module API
 | 
			
		|||
      delete ':id/protected_branches/:name', requirements: BRANCH_ENDPOINT_REQUIREMENTS do
 | 
			
		||||
        protected_branch = user_project.protected_branches.find_by!(name: params[:name])
 | 
			
		||||
 | 
			
		||||
        destroy_conditionally!(protected_branch)
 | 
			
		||||
        destroy_conditionally!(protected_branch) do
 | 
			
		||||
          destroy_service = ::ProtectedBranches::DestroyService.new(user_project, current_user)
 | 
			
		||||
          destroy_service.execute(protected_branch)
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,16 @@
 | 
			
		|||
require('spec_helper')
 | 
			
		||||
 | 
			
		||||
describe Projects::ProtectedBranchesController do
 | 
			
		||||
  let(:project) { create(:project, :repository) }
 | 
			
		||||
  let(:protected_branch) { create(:protected_branch, project: project) }
 | 
			
		||||
  let(:project_params) { { namespace_id: project.namespace.to_param, project_id: project } }
 | 
			
		||||
  let(:base_params) { project_params.merge(id: protected_branch.id) }
 | 
			
		||||
  let(:user) { create(:user) }
 | 
			
		||||
 | 
			
		||||
  before do
 | 
			
		||||
    project.add_master(user)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe "GET #index" do
 | 
			
		||||
    let(:project) { create(:project_empty_repo, :public) }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -8,4 +18,91 @@ describe Projects::ProtectedBranchesController do
 | 
			
		|||
      get(:index, namespace_id: project.namespace.to_param, project_id: project)
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe "POST #create" do
 | 
			
		||||
    let(:master_access_level) { [{ access_level: Gitlab::Access::MASTER }] }
 | 
			
		||||
    let(:access_level_params) do
 | 
			
		||||
      { merge_access_levels_attributes: master_access_level,
 | 
			
		||||
        push_access_levels_attributes: master_access_level }
 | 
			
		||||
    end
 | 
			
		||||
    let(:create_params) { attributes_for(:protected_branch).merge(access_level_params) }
 | 
			
		||||
 | 
			
		||||
    before do
 | 
			
		||||
      sign_in(user)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'creates the protected branch rule' do
 | 
			
		||||
      expect do
 | 
			
		||||
        post(:create, project_params.merge(protected_branch: create_params))
 | 
			
		||||
      end.to change(ProtectedBranch, :count).by(1)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context 'when a policy restricts rule deletion' do
 | 
			
		||||
      before do
 | 
			
		||||
        policy = instance_double(ProtectedBranchPolicy, can?: false)
 | 
			
		||||
        allow(ProtectedBranchPolicy).to receive(:new).and_return(policy)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      it "prevents creation of the protected branch rule" do
 | 
			
		||||
        post(:create, project_params.merge(protected_branch: create_params))
 | 
			
		||||
 | 
			
		||||
        expect(ProtectedBranch.count).to eq 0
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe "PUT #update" do
 | 
			
		||||
    let(:update_params) { { name: 'new_name' } }
 | 
			
		||||
 | 
			
		||||
    before do
 | 
			
		||||
      sign_in(user)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it 'updates the protected branch rule' do
 | 
			
		||||
      put(:update, base_params.merge(protected_branch: update_params))
 | 
			
		||||
 | 
			
		||||
      expect(protected_branch.reload.name).to eq('new_name')
 | 
			
		||||
      expect(json_response["name"]).to eq('new_name')
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context 'when a policy restricts rule deletion' do
 | 
			
		||||
      before do
 | 
			
		||||
        policy = instance_double(ProtectedBranchPolicy, can?: false)
 | 
			
		||||
        allow(ProtectedBranchPolicy).to receive(:new).and_return(policy)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      it "prevents update of the protected branch rule" do
 | 
			
		||||
        old_name = protected_branch.name
 | 
			
		||||
 | 
			
		||||
        put(:update, base_params.merge(protected_branch: update_params))
 | 
			
		||||
 | 
			
		||||
        expect(protected_branch.reload.name).to eq(old_name)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  describe "DELETE #destroy" do
 | 
			
		||||
    before do
 | 
			
		||||
      sign_in(user)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it "deletes the protected branch rule" do
 | 
			
		||||
      delete(:destroy, base_params)
 | 
			
		||||
 | 
			
		||||
      expect { ProtectedBranch.find(protected_branch.id) }.to raise_error(ActiveRecord::RecordNotFound)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context 'when a policy restricts rule deletion' do
 | 
			
		||||
      before do
 | 
			
		||||
        policy = instance_double(ProtectedBranchPolicy, can?: false)
 | 
			
		||||
        allow(ProtectedBranchPolicy).to receive(:new).and_return(policy)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      it "prevents deletion of the protected branch rule" do
 | 
			
		||||
        delete(:destroy, base_params)
 | 
			
		||||
 | 
			
		||||
        expect(response.status).to eq(403)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,22 @@
 | 
			
		|||
require 'spec_helper'
 | 
			
		||||
 | 
			
		||||
describe ProtectedBranchPolicy do
 | 
			
		||||
  let(:user) { create(:user) }
 | 
			
		||||
  let(:name) { 'feature' }
 | 
			
		||||
  let(:protected_branch) { create(:protected_branch, name: name) }
 | 
			
		||||
  let(:project) { protected_branch.project }
 | 
			
		||||
 | 
			
		||||
  subject { described_class.new(user, protected_branch) }
 | 
			
		||||
 | 
			
		||||
  it 'branches can be updated via project masters' do
 | 
			
		||||
    project.add_master(user)
 | 
			
		||||
 | 
			
		||||
    is_expected.to be_allowed(:update_protected_branch)
 | 
			
		||||
  end
 | 
			
		||||
 | 
			
		||||
  it "branches can't be updated by guests" do
 | 
			
		||||
    project.add_guest(user)
 | 
			
		||||
 | 
			
		||||
    is_expected.to be_disallowed(:update_protected_branch)
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -193,6 +193,19 @@ describe API::ProtectedBranches do
 | 
			
		|||
          expect(json_response['merge_access_levels'][0]['access_level']).to eq(Gitlab::Access::MASTER)
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      context 'when a policy restricts rule deletion' do
 | 
			
		||||
        before do
 | 
			
		||||
          policy = instance_double(ProtectedBranchPolicy, can?: false)
 | 
			
		||||
          expect(ProtectedBranchPolicy).to receive(:new).and_return(policy)
 | 
			
		||||
        end
 | 
			
		||||
 | 
			
		||||
        it "prevents deletion of the protected branch rule" do
 | 
			
		||||
          post post_endpoint, name: branch_name
 | 
			
		||||
 | 
			
		||||
          expect(response).to have_gitlab_http_status(403)
 | 
			
		||||
        end
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context 'when authenticated as a guest' do
 | 
			
		||||
| 
						 | 
				
			
			@ -209,18 +222,20 @@ describe API::ProtectedBranches do
 | 
			
		|||
  end
 | 
			
		||||
 | 
			
		||||
  describe "DELETE /projects/:id/protected_branches/unprotect/:branch" do
 | 
			
		||||
    let(:delete_endpoint) { api("/projects/#{project.id}/protected_branches/#{branch_name}", user) }
 | 
			
		||||
 | 
			
		||||
    before do
 | 
			
		||||
      project.add_master(user)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it "unprotects a single branch" do
 | 
			
		||||
      delete api("/projects/#{project.id}/protected_branches/#{branch_name}", user)
 | 
			
		||||
      delete delete_endpoint
 | 
			
		||||
 | 
			
		||||
      expect(response).to have_gitlab_http_status(204)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it_behaves_like '412 response' do
 | 
			
		||||
      let(:request) { api("/projects/#{project.id}/protected_branches/#{branch_name}", user) }
 | 
			
		||||
      let(:request) { delete_endpoint }
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    it "returns 404 if branch does not exist" do
 | 
			
		||||
| 
						 | 
				
			
			@ -229,11 +244,24 @@ describe API::ProtectedBranches do
 | 
			
		|||
      expect(response).to have_gitlab_http_status(404)
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context 'when a policy restricts rule deletion' do
 | 
			
		||||
      before do
 | 
			
		||||
        policy = instance_double(ProtectedBranchPolicy, can?: false)
 | 
			
		||||
        expect(ProtectedBranchPolicy).to receive(:new).and_return(policy)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      it "prevents deletion of the protected branch rule" do
 | 
			
		||||
        delete delete_endpoint
 | 
			
		||||
 | 
			
		||||
        expect(response).to have_gitlab_http_status(403)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context 'when branch has a wildcard in its name' do
 | 
			
		||||
      let(:protected_name) { 'feature*' }
 | 
			
		||||
 | 
			
		||||
      it "unprotects a wildcard branch" do
 | 
			
		||||
        delete api("/projects/#{project.id}/protected_branches/#{branch_name}", user)
 | 
			
		||||
        delete delete_endpoint
 | 
			
		||||
 | 
			
		||||
        expect(response).to have_gitlab_http_status(204)
 | 
			
		||||
      end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -35,5 +35,18 @@ describe ProtectedBranches::CreateService do
 | 
			
		|||
        expect { service.execute }.to raise_error(Gitlab::Access::AccessDeniedError)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context 'when a policy restricts rule creation' do
 | 
			
		||||
      before do
 | 
			
		||||
        policy = instance_double(ProtectedBranchPolicy, can?: false)
 | 
			
		||||
        expect(ProtectedBranchPolicy).to receive(:new).and_return(policy)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      it "prevents creation of the protected branch rule" do
 | 
			
		||||
        expect do
 | 
			
		||||
          service.execute
 | 
			
		||||
        end.to raise_error(Gitlab::Access::AccessDeniedError)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,30 @@
 | 
			
		|||
require 'spec_helper'
 | 
			
		||||
 | 
			
		||||
describe ProtectedBranches::DestroyService do
 | 
			
		||||
  let(:protected_branch) { create(:protected_branch) }
 | 
			
		||||
  let(:project) { protected_branch.project }
 | 
			
		||||
  let(:user) { project.owner }
 | 
			
		||||
 | 
			
		||||
  describe '#execute' do
 | 
			
		||||
    subject(:service) { described_class.new(project, user) }
 | 
			
		||||
 | 
			
		||||
    it 'destroys a protected branch' do
 | 
			
		||||
      service.execute(protected_branch)
 | 
			
		||||
 | 
			
		||||
      expect(protected_branch).to be_destroyed
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context 'when a policy restricts rule deletion' do
 | 
			
		||||
      before do
 | 
			
		||||
        policy = instance_double(ProtectedBranchPolicy, can?: false)
 | 
			
		||||
        expect(ProtectedBranchPolicy).to receive(:new).and_return(policy)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      it "prevents deletion of the protected branch rule" do
 | 
			
		||||
        expect do
 | 
			
		||||
          service.execute(protected_branch)
 | 
			
		||||
        end.to raise_error(Gitlab::Access::AccessDeniedError)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			@ -22,5 +22,16 @@ describe ProtectedBranches::UpdateService do
 | 
			
		|||
        expect { service.execute(protected_branch) }.to raise_error(Gitlab::Access::AccessDeniedError)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    context 'when a policy restricts rule creation' do
 | 
			
		||||
      before do
 | 
			
		||||
        policy = instance_double(ProtectedBranchPolicy, can?: false)
 | 
			
		||||
        expect(ProtectedBranchPolicy).to receive(:new).and_return(policy)
 | 
			
		||||
      end
 | 
			
		||||
 | 
			
		||||
      it "prevents creation of the protected branch rule" do
 | 
			
		||||
        expect { service.execute(protected_branch) }.to raise_error(Gitlab::Access::AccessDeniedError)
 | 
			
		||||
      end
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,17 @@
 | 
			
		|||
require 'spec_helper'
 | 
			
		||||
 | 
			
		||||
describe ProtectedTags::DestroyService do
 | 
			
		||||
  let(:protected_tag) { create(:protected_tag) }
 | 
			
		||||
  let(:project) { protected_tag.project }
 | 
			
		||||
  let(:user) { project.owner }
 | 
			
		||||
 | 
			
		||||
  describe '#execute' do
 | 
			
		||||
    subject(:service) { described_class.new(project, user) }
 | 
			
		||||
 | 
			
		||||
    it 'destroy a protected tag' do
 | 
			
		||||
      service.execute(protected_tag)
 | 
			
		||||
 | 
			
		||||
      expect(protected_tag).to be_destroyed
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
		Loading…
	
		Reference in New Issue