Commit c1d51e09 authored by James Lopez's avatar James Lopez

Merge branch '52954-allow-developers-to-delete-tags' into 'master'

Allow developers to delete tags

Closes #52954

See merge request gitlab-org/gitlab-ce!29668
parents 69e1bd38 53b17f03
...@@ -8,8 +8,7 @@ class Projects::TagsController < Projects::ApplicationController ...@@ -8,8 +8,7 @@ class Projects::TagsController < Projects::ApplicationController
# Authorize # Authorize
before_action :require_non_empty_project before_action :require_non_empty_project
before_action :authorize_download_code! before_action :authorize_download_code!
before_action :authorize_push_code!, only: [:new, :create] before_action :authorize_admin_tag!, only: [:new, :create, :destroy]
before_action :authorize_admin_project!, only: [:destroy]
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def index def index
......
...@@ -297,6 +297,7 @@ class ProjectPolicy < BasePolicy ...@@ -297,6 +297,7 @@ class ProjectPolicy < BasePolicy
end end
rule { (mirror_available & can?(:admin_project)) | admin }.enable :admin_remote_mirror rule { (mirror_available & can?(:admin_project)) | admin }.enable :admin_remote_mirror
rule { can?(:push_code) }.enable :admin_tag
rule { archived }.policy do rule { archived }.policy do
prevent :push_code prevent :push_code
......
...@@ -26,10 +26,8 @@ ...@@ -26,10 +26,8 @@
.row-fixed-content.controls.flex-row .row-fixed-content.controls.flex-row
= render 'projects/buttons/download', project: @project, ref: tag.name, pipeline: @tags_pipelines[tag.name] = render 'projects/buttons/download', project: @project, ref: tag.name, pipeline: @tags_pipelines[tag.name]
- if can?(current_user, :push_code, @project) - if can?(current_user, :admin_tag, @project)
= link_to edit_project_tag_release_path(@project, tag.name), class: 'btn btn-edit has-tooltip', title: s_('TagsPage|Edit release notes'), data: { container: "body" } do = link_to edit_project_tag_release_path(@project, tag.name), class: 'btn btn-edit has-tooltip', title: s_('TagsPage|Edit release notes'), data: { container: "body" } do
= icon("pencil") = icon("pencil")
= link_to project_tag_path(@project, tag.name), class: "btn btn-remove remove-row has-tooltip prepend-left-10 #{protected_tag?(@project, tag) ? 'disabled' : ''}", title: s_('TagsPage|Delete tag'), method: :delete, data: { confirm: s_('TagsPage|Deleting the %{tag_name} tag cannot be undone. Are you sure?') % { tag_name: tag.name }, container: 'body' }, remote: true do
- if can?(current_user, :admin_project, @project) = icon("trash-o")
= link_to project_tag_path(@project, tag.name), class: "btn btn-remove remove-row has-tooltip prepend-left-10 #{protected_tag?(@project, tag) ? 'disabled' : ''}", title: s_('TagsPage|Delete tag'), method: :delete, data: { confirm: s_('TagsPage|Deleting the %{tag_name} tag cannot be undone. Are you sure?') % { tag_name: tag.name }, container: 'body' }, remote: true do
= icon("trash-o")
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
- tags_sort_options_hash.each do |value, title| - tags_sort_options_hash.each do |value, title|
%li %li
= link_to title, filter_tags_path(sort: value), class: ("is-active" if @sort == value) = link_to title, filter_tags_path(sort: value), class: ("is-active" if @sort == value)
- if can?(current_user, :push_code, @project) - if can?(current_user, :admin_tag, @project)
= link_to new_project_tag_path(@project), class: 'btn btn-success new-tag-btn' do = link_to new_project_tag_path(@project), class: 'btn btn-success new-tag-btn' do
= s_('TagsPage|New tag') = s_('TagsPage|New tag')
= link_to project_tags_path(@project, rss_url_options), title: _("Tags feed"), class: 'btn d-none d-sm-inline-block has-tooltip' do = link_to project_tags_path(@project, rss_url_options), title: _("Tags feed"), class: 'btn d-none d-sm-inline-block has-tooltip' do
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
= s_("TagsPage|Can't find HEAD commit for this tag") = s_("TagsPage|Can't find HEAD commit for this tag")
.nav-controls .nav-controls
- if can?(current_user, :push_code, @project) - if can?(current_user, :admin_tag, @project)
= link_to edit_project_tag_release_path(@project, @tag.name), class: 'btn btn-edit controls-item has-tooltip', title: s_('TagsPage|Edit release notes') do = link_to edit_project_tag_release_path(@project, @tag.name), class: 'btn btn-edit controls-item has-tooltip', title: s_('TagsPage|Edit release notes') do
= icon("pencil") = icon("pencil")
= link_to project_tree_path(@project, @tag.name), class: 'btn controls-item has-tooltip', title: s_('TagsPage|Browse files') do = link_to project_tree_path(@project, @tag.name), class: 'btn controls-item has-tooltip', title: s_('TagsPage|Browse files') do
...@@ -28,7 +28,7 @@ ...@@ -28,7 +28,7 @@
= icon('history') = icon('history')
.btn-container.controls-item .btn-container.controls-item
= render 'projects/buttons/download', project: @project, ref: @tag.name = render 'projects/buttons/download', project: @project, ref: @tag.name
- if can?(current_user, :push_code, @project) && can?(current_user, :admin_project, @project) - if can?(current_user, :admin_tag, @project)
.btn-container.controls-item-full .btn-container.controls-item-full
= link_to project_tag_path(@project, @tag.name), class: "btn btn-remove remove-row has-tooltip #{protected_tag?(@project, @tag) ? 'disabled' : ''}", title: s_('TagsPage|Delete tag'), method: :delete, data: { confirm: s_('TagsPage|Deleting the %{tag_name} tag cannot be undone. Are you sure?') % { tag_name: @tag.name } } do = link_to project_tag_path(@project, @tag.name), class: "btn btn-remove remove-row has-tooltip #{protected_tag?(@project, @tag) ? 'disabled' : ''}", title: s_('TagsPage|Delete tag'), method: :delete, data: { confirm: s_('TagsPage|Deleting the %{tag_name} tag cannot be undone. Are you sure?') % { tag_name: @tag.name } } do
%i.fa.fa-trash-o %i.fa.fa-trash-o
......
---
title: Allow developers to delete tags
merge_request: 29668
author:
type: changed
...@@ -95,6 +95,7 @@ The following table depicts the various user permission levels in a project. ...@@ -95,6 +95,7 @@ The following table depicts the various user permission levels in a project.
| Dismiss vulnerability **[ULTIMATE]** | | | ✓ | ✓ | ✓ | | Dismiss vulnerability **[ULTIMATE]** | | | ✓ | ✓ | ✓ |
| Apply code change suggestions | | | ✓ | ✓ | ✓ | | Apply code change suggestions | | | ✓ | ✓ | ✓ |
| Create and edit wiki pages | | | ✓ | ✓ | ✓ | | Create and edit wiki pages | | | ✓ | ✓ | ✓ |
| Rewrite/remove Git tags | | | ✓ | ✓ | ✓ |
| Use environment terminals | | | | ✓ | ✓ | | Use environment terminals | | | | ✓ | ✓ |
| Run Web IDE's Interactive Web Terminals **[ULTIMATE ONLY]** | | | | ✓ | ✓ | | Run Web IDE's Interactive Web Terminals **[ULTIMATE ONLY]** | | | | ✓ | ✓ |
| Add new team members | | | | ✓ | ✓ | | Add new team members | | | | ✓ | ✓ |
...@@ -102,7 +103,6 @@ The following table depicts the various user permission levels in a project. ...@@ -102,7 +103,6 @@ The following table depicts the various user permission levels in a project.
| Push to protected branches | | | | ✓ | ✓ | | Push to protected branches | | | | ✓ | ✓ |
| Turn on/off protected branch push for devs | | | | ✓ | ✓ | | Turn on/off protected branch push for devs | | | | ✓ | ✓ |
| Enable/disable tag protections | | | | ✓ | ✓ | | Enable/disable tag protections | | | | ✓ | ✓ |
| Rewrite/remove Git tags | | | | ✓ | ✓ |
| Edit project | | | | ✓ | ✓ | | Edit project | | | | ✓ | ✓ |
| Add deploy keys to project | | | | ✓ | ✓ | | Add deploy keys to project | | | | ✓ | ✓ |
| Configure project hooks | | | | ✓ | ✓ | | Configure project hooks | | | | ✓ | ✓ |
......
...@@ -235,6 +235,10 @@ module API ...@@ -235,6 +235,10 @@ module API
authorize! :push_code, user_project authorize! :push_code, user_project
end end
def authorize_admin_tag
authorize! :admin_tag, user_project
end
def authorize_admin_project def authorize_admin_project
authorize! :admin_project, user_project authorize! :admin_project, user_project
end end
......
...@@ -55,7 +55,7 @@ module API ...@@ -55,7 +55,7 @@ module API
optional :release_description, type: String, desc: 'Specifying release notes stored in the GitLab database (deprecated in GitLab 11.7)' optional :release_description, type: String, desc: 'Specifying release notes stored in the GitLab database (deprecated in GitLab 11.7)'
end end
post ':id/repository/tags' do post ':id/repository/tags' do
authorize_push_project authorize_admin_tag
result = ::Tags::CreateService.new(user_project, current_user) result = ::Tags::CreateService.new(user_project, current_user)
.execute(params[:tag_name], params[:ref], params[:message]) .execute(params[:tag_name], params[:ref], params[:message])
...@@ -87,7 +87,7 @@ module API ...@@ -87,7 +87,7 @@ module API
requires :tag_name, type: String, desc: 'The name of the tag' requires :tag_name, type: String, desc: 'The name of the tag'
end end
delete ':id/repository/tags/:tag_name', requirements: TAG_ENDPOINT_REQUIREMENTS do delete ':id/repository/tags/:tag_name', requirements: TAG_ENDPOINT_REQUIREMENTS do
authorize_push_project authorize_admin_tag
tag = user_project.repository.find_tag(params[:tag_name]) tag = user_project.repository.find_tag(params[:tag_name])
not_found!('Tag') unless tag not_found!('Tag') unless tag
......
...@@ -19,7 +19,7 @@ module Gitlab ...@@ -19,7 +19,7 @@ module Gitlab
return unless tag_name return unless tag_name
logger.log_timed(LOG_MESSAGES[:tag_checks]) do logger.log_timed(LOG_MESSAGES[:tag_checks]) do
if tag_exists? && user_access.cannot_do_action?(:admin_project) if tag_exists? && user_access.cannot_do_action?(:admin_tag)
raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:change_existing_tags] raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:change_existing_tags]
end end
end end
......
...@@ -45,7 +45,7 @@ module Gitlab ...@@ -45,7 +45,7 @@ module Gitlab
if protected?(ProtectedTag, project, ref) if protected?(ProtectedTag, project, ref)
protected_tag_accessible_to?(ref, action: :create) protected_tag_accessible_to?(ref, action: :create)
else else
user.can?(:push_code, project) user.can?(:admin_tag, project)
end end
end end
......
require 'spec_helper' require 'spec_helper'
describe 'Maintainer creates tag' do describe 'Developer creates tag' do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project, :repository, namespace: user.namespace) } let(:group) { create(:group) }
let(:project) { create(:project, :repository, namespace: group) }
before do before do
project.add_maintainer(user) project.add_developer(user)
sign_in(user) sign_in(user)
end end
......
require 'spec_helper' require 'spec_helper'
describe 'Maintainer deletes tag' do describe 'Developer deletes tag' do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project, :repository, namespace: user.namespace) } let(:group) { create(:group) }
let(:project) { create(:project, :repository, namespace: group) }
before do before do
project.add_maintainer(user) project.add_developer(user)
sign_in(user) sign_in(user)
visit project_tags_path(project) visit project_tags_path(project)
end end
......
require 'spec_helper' require 'spec_helper'
describe 'Maintainer updates tag' do describe 'Developer updates tag' do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:project) { create(:project, :repository, namespace: user.namespace) } let(:group) { create(:group) }
let(:project) { create(:project, :repository, namespace: group) }
before do before do
project.add_maintainer(user) project.add_developer(user)
sign_in(user) sign_in(user)
visit project_tags_path(project) visit project_tags_path(project)
end end
......
require 'spec_helper' require 'spec_helper'
describe 'Maintainer views tags' do describe 'Developer views tags' do
let(:user) { create(:user) } let(:user) { create(:user) }
let(:group) { create(:group) }
before do before do
project.add_maintainer(user) project.add_maintainer(user)
...@@ -9,7 +10,7 @@ describe 'Maintainer views tags' do ...@@ -9,7 +10,7 @@ describe 'Maintainer views tags' do
end end
context 'when project has no tags' do context 'when project has no tags' do
let(:project) { create(:project_empty_repo) } let(:project) { create(:project_empty_repo, namespace: group) }
before do before do
visit project_path(project) visit project_path(project)
...@@ -25,7 +26,7 @@ describe 'Maintainer views tags' do ...@@ -25,7 +26,7 @@ describe 'Maintainer views tags' do
end end
context 'when project has tags' do context 'when project has tags' do
let(:project) { create(:project, :repository, namespace: user.namespace) } let(:project) { create(:project, :repository, namespace: group) }
let(:repository) { project.repository } let(:repository) { project.repository }
before do before do
......
...@@ -8,9 +8,8 @@ describe Gitlab::Checks::TagCheck do ...@@ -8,9 +8,8 @@ describe Gitlab::Checks::TagCheck do
describe '#validate!' do describe '#validate!' do
let(:ref) { 'refs/tags/v1.0.0' } let(:ref) { 'refs/tags/v1.0.0' }
it 'raises an error' do it 'raises an error when user does not have access' do
allow(user_access).to receive(:can_do_action?).with(:push_code).and_return(true) allow(user_access).to receive(:can_do_action?).with(:admin_tag).and_return(false)
expect(user_access).to receive(:can_do_action?).with(:admin_project).and_return(false)
expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to change existing tags on this project.') expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to change existing tags on this project.')
end end
......
...@@ -831,7 +831,7 @@ describe Gitlab::GitAccess do ...@@ -831,7 +831,7 @@ describe Gitlab::GitAccess do
push_master: true, push_master: true,
push_protected_branch: false, push_protected_branch: false,
push_remove_protected_branch: false, push_remove_protected_branch: false,
push_tag: false, push_tag: true,
push_new_tag: true, push_new_tag: true,
push_all: false, push_all: false,
merge_into_protected_branch: false merge_into_protected_branch: false
......
...@@ -36,7 +36,7 @@ describe ProjectPolicy do ...@@ -36,7 +36,7 @@ describe ProjectPolicy do
let(:developer_permissions) do let(:developer_permissions) do
%i[ %i[
admin_milestone admin_merge_request update_merge_request create_commit_status admin_tag admin_milestone admin_merge_request update_merge_request create_commit_status
update_commit_status create_build update_build create_pipeline update_commit_status create_build update_build create_pipeline
update_pipeline create_merge_request_from create_wiki push_code update_pipeline create_merge_request_from create_wiki push_code
resolve_note create_container_image update_container_image destroy_container_image resolve_note create_container_image update_container_image destroy_container_image
......
...@@ -10,7 +10,7 @@ describe API::Tags do ...@@ -10,7 +10,7 @@ describe API::Tags do
let(:current_user) { nil } let(:current_user) { nil }
before do before do
project.add_maintainer(user) project.add_developer(user)
end end
describe 'GET /projects/:id/repository/tags' do describe 'GET /projects/:id/repository/tags' do
......
...@@ -17,6 +17,7 @@ RSpec.shared_examples 'archived project policies' do ...@@ -17,6 +17,7 @@ RSpec.shared_examples 'archived project policies' do
upload_file upload_file
resolve_note resolve_note
award_emoji award_emoji
admin_tag
] ]
end end
......
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