Commit f5a3d8f7 authored by Robert Speicher's avatar Robert Speicher

Merge branch 'dm-archived-read-only' into 'master'

Make archived projects completely read-only

Closes #44788

See merge request gitlab-org/gitlab-ce!18136
parents 64f91be0 eae2ed33
......@@ -317,10 +317,10 @@ Please check your network connection and try again.`;
<note-signed-out-widget v-if="!isLoggedIn" />
<discussion-locked-widget
issuable-type="issue"
v-else-if="!canCreateNote"
v-else-if="isLocked(getNoteableData) && !canCreateNote"
/>
<ul
v-else
v-else-if="canCreateNote"
class="notes notes-form timeline">
<li class="timeline-entry">
<div class="timeline-entry-inner">
......
......@@ -40,6 +40,10 @@ export default {
type: Boolean,
required: true,
},
canAwardEmoji: {
type: Boolean,
required: true,
},
canDelete: {
type: Boolean,
required: true,
......@@ -74,9 +78,6 @@ export default {
shouldShowActionsDropdown() {
return this.currentUserId && (this.canEdit || this.canReportAsAbuse);
},
canAddAwardEmoji() {
return this.currentUserId;
},
isAuthoredByCurrentUser() {
return this.authorId === this.currentUserId;
},
......@@ -149,7 +150,7 @@ export default {
</button>
</div>
<div
v-if="canAddAwardEmoji"
v-if="canAwardEmoji"
class="note-actions-item">
<a
v-tooltip
......
......@@ -28,6 +28,10 @@ export default {
type: Number,
required: true,
},
canAwardEmoji: {
type: Boolean,
required: true,
},
},
computed: {
...mapGetters(['getUserData']),
......@@ -67,9 +71,6 @@ export default {
isAuthoredByMe() {
return this.noteAuthorId === this.getUserData.id;
},
isLoggedIn() {
return this.getUserData.id;
},
},
created() {
this.emojiSmiling = emojiSmiling;
......@@ -156,7 +157,7 @@ export default {
return title;
},
handleAward(awardName) {
if (!this.isLoggedIn) {
if (!this.canAwardEmoji) {
return;
}
......@@ -208,7 +209,7 @@ export default {
</span>
</button>
<div
v-if="isLoggedIn"
v-if="canAwardEmoji"
class="award-menu-holder">
<button
v-tooltip
......
......@@ -112,6 +112,7 @@ export default {
:note-author-id="note.author.id"
:awards="note.award_emoji"
:toggle-award-path="note.toggle_award_path"
:can-award-emoji="note.current_user.can_award_emoji"
/>
<note-attachment
v-if="note.attachment"
......
......@@ -177,6 +177,7 @@ export default {
:note-id="note.id"
:access-level="note.human_access"
:can-edit="note.current_user.can_edit"
:can-award-emoji="note.current_user.can_award_emoji"
:can-delete="note.current_user.can_edit"
:can-report-as-abuse="canReportAsAbuse"
:report-abuse-path="note.report_abuse_path"
......
module ChecksCollaboration
def can_collaborate_with_project?(project, ref: nil)
return true if can?(current_user, :push_code, project)
can_create_merge_request =
can?(current_user, :create_merge_request_in, project) &&
current_user.already_forked?(project)
can_create_merge_request ||
user_access(project).can_push_to_branch?(ref)
end
# rubocop:disable Gitlab/ModuleWithInstanceVariables
# enabling this so we can easily cache the user access value as it might be
# used across multiple calls in the view
def user_access(project)
@user_access ||= {}
@user_access[project] ||= Gitlab::UserAccess.new(current_user, project: project)
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
end
class Projects::ApplicationController < ApplicationController
include RoutableActions
include ChecksCollaboration
skip_before_action :authenticate_user!
before_action :project
......@@ -31,14 +32,6 @@ class Projects::ApplicationController < ApplicationController
@repository ||= project.repository
end
def can_collaborate_with_project?(project = nil, ref: nil)
project ||= @project
can?(current_user, :push_code, project) ||
(current_user && current_user.already_forked?(project)) ||
user_access(project).can_push_to_branch?(ref)
end
def authorize_action!(action)
unless can?(current_user, action, project)
return access_denied!
......@@ -91,9 +84,4 @@ class Projects::ApplicationController < ApplicationController
def check_issues_available!
return render_404 unless @project.feature_available?(:issues, current_user)
end
def user_access(project)
@user_access ||= {}
@user_access[project] ||= Gitlab::UserAccess.new(current_user, project: project)
end
end
......@@ -20,7 +20,7 @@ class Projects::IssuesController < Projects::ApplicationController
before_action :authorize_update_issuable!, only: [:edit, :update, :move]
# Allow create a new branch and empty WIP merge request from current issue
before_action :authorize_create_merge_request!, only: [:create_merge_request]
before_action :authorize_create_merge_request_from!, only: [:create_merge_request]
respond_to :html
......
......@@ -5,7 +5,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
skip_before_action :merge_request
before_action :whitelist_query_limiting, only: [:create]
before_action :authorize_create_merge_request!
before_action :authorize_create_merge_request_from!
before_action :apply_diff_view_cookie!, only: [:diffs, :diff_for_path]
before_action :build_merge_request, except: [:create]
......
......@@ -12,6 +12,7 @@ class MergeRequestTargetProjectFinder
if @source_project.fork_network
@source_project.fork_network.projects
.public_or_visible_to_user(current_user)
.non_archived
.with_feature_available_for_user(:merge_requests, current_user)
else
Project.where(id: source_project)
......
......@@ -59,7 +59,7 @@ module BlobHelper
button_tag label, class: "#{common_classes} disabled has-tooltip", title: "It is not possible to #{action} files that are stored in LFS using the web interface", data: { container: 'body' }
elsif can_modify_blob?(blob, project, ref)
button_tag label, class: "#{common_classes}", 'data-target' => "#modal-#{modal_type}-blob", 'data-toggle' => 'modal'
elsif can?(current_user, :fork_project, project)
elsif can?(current_user, :fork_project, project) && can?(current_user, :create_merge_request_in, project)
edit_fork_button_tag(common_classes, project, label, edit_modify_file_fork_params(action), action)
end
end
......@@ -280,7 +280,7 @@ module BlobHelper
options << link_to("submit an issue", new_project_issue_path(project))
end
merge_project = can?(current_user, :create_merge_request, project) ? project : (current_user && current_user.fork_of(project))
merge_project = merge_request_source_project_for_project(@project)
if merge_project
options << link_to("create a merge request", project_new_merge_request_path(project))
end
......@@ -334,7 +334,7 @@ module BlobHelper
# Web IDE (Beta) requires the user to have this feature enabled
elsif !current_user || (current_user && can_modify_blob?(blob, project, ref))
edit_link_tag(text, edit_path, common_classes)
elsif current_user && can?(current_user, :fork_project, project)
elsif can?(current_user, :fork_project, project) && can?(current_user, :create_merge_request_in, project)
edit_fork_button_tag(common_classes, project, text, edit_blob_fork_params(edit_path))
end
end
......
......@@ -163,7 +163,7 @@ module CommitsHelper
tooltip = "#{action.capitalize} this #{commit.change_type_title(current_user)} in a new merge request" if has_tooltip
btn_class = "btn btn-#{btn_class}" unless btn_class.nil?
if can_collaborate_with_project?
if can_collaborate_with_project?(@project)
link_to action.capitalize, "#modal-#{action}-commit", 'data-toggle' => 'modal', 'data-container' => 'body', title: (tooltip if has_tooltip), class: "#{btn_class} #{'has-tooltip' if has_tooltip}"
elsif can?(current_user, :fork_project, @project)
continue_params = {
......
......@@ -3,7 +3,7 @@ module CompareHelper
from.present? &&
to.present? &&
from != to &&
can?(current_user, :create_merge_request, project) &&
can?(current_user, :create_merge_request_from, project) &&
project.repository.branch_exists?(from) &&
project.repository.branch_exists?(to)
end
......
......@@ -82,8 +82,8 @@ module IssuesHelper
names.to_sentence
end
def award_state_class(awards, current_user)
if !current_user
def award_state_class(awardable, awards, current_user)
if !can?(current_user, :award_emoji, awardable)
"disabled"
elsif current_user && awards.find { |a| a.user_id == current_user.id }
"active"
......@@ -126,6 +126,17 @@ module IssuesHelper
link_to link_text, path
end
def show_new_issue_link?(project)
return false unless project
return false if project.archived?
# We want to show the link to users that are not signed in, that way they
# get directed to the sign-in/sign-up flow and afterwards to the new issue page.
return true unless current_user
can?(current_user, :create_issue, project)
end
# Required for Banzai::Filter::IssueReferenceFilter
module_function :url_for_issue
module_function :url_for_internal_issue
......
......@@ -138,6 +138,18 @@ module MergeRequestsHelper
end
end
def merge_request_source_project_for_project(project = @project)
unless can?(current_user, :create_merge_request_in, project)
return nil
end
if can?(current_user, :create_merge_request_from, project)
project
else
current_user.fork_of(project)
end
end
def merge_params_ee(merge_request)
{}
end
......
......@@ -6,10 +6,6 @@ module NotesHelper
end
end
def note_editable?(note)
Ability.can_edit_note?(current_user, note)
end
def note_supports_quick_actions?(note)
Notes::QuickActionsService.supported?(note)
end
......
......@@ -46,10 +46,6 @@ class Ability
end
end
def can_edit_note?(user, note)
allowed?(user, :edit_note, note)
end
def allowed?(user, action, subject = :global, opts = {})
if subject.is_a?(Hash)
opts, subject = subject, :global
......
......@@ -79,11 +79,7 @@ module Awardable
end
def user_can_award?(current_user, name)
if user_authored?(current_user)
!awardable_votes?(normalize_name(name))
else
true
end
awardable_by_user?(current_user, name) && Ability.allowed?(current_user, :award_emoji, self)
end
def user_authored?(current_user)
......@@ -119,4 +115,12 @@ module Awardable
def normalize_name(name)
Gitlab::Emoji.normalize_emoji_name(name)
end
def awardable_by_user?(current_user, name)
if user_authored?(current_user)
!awardable_votes?(normalize_name(name))
else
true
end
end
end
......@@ -11,7 +11,7 @@ module Ci
end
condition(:owner_of_job) do
can?(:developer_access) && @subject.triggered_by?(@user)
@subject.triggered_by?(@user)
end
rule { protected_ref }.policy do
......@@ -19,6 +19,6 @@ module Ci
prevent :erase_build
end
rule { can?(:master_access) | owner_of_job }.enable :erase_build
rule { can?(:admin_build) | (can?(:update_build) & owner_of_job) }.enable :erase_build
end
end
......@@ -7,23 +7,17 @@ module Ci
end
condition(:owner_of_schedule) do
can?(:developer_access) && pipeline_schedule.owned_by?(@user)
pipeline_schedule.owned_by?(@user)
end
condition(:non_owner_of_schedule) do
!pipeline_schedule.owned_by?(@user)
end
rule { can?(:developer_access) }.policy do
enable :play_pipeline_schedule
end
rule { can?(:create_pipeline) }.enable :play_pipeline_schedule
rule { can?(:master_access) | owner_of_schedule }.policy do
rule { can?(:admin_pipeline) | (can?(:update_build) & owner_of_schedule) }.policy do
enable :update_pipeline_schedule
enable :admin_pipeline_schedule
end
rule { can?(:master_access) & non_owner_of_schedule }.policy do
rule { can?(:admin_pipeline_schedule) & ~owner_of_schedule }.policy do
enable :take_ownership_pipeline_schedule
end
......
......@@ -18,9 +18,7 @@ class IssuablePolicy < BasePolicy
rule { locked & ~is_project_member }.policy do
prevent :create_note
prevent :update_note
prevent :admin_note
prevent :resolve_note
prevent :edit_note
end
end
class NotePolicy < BasePolicy
delegate { @subject.project }
delegate { @subject.noteable if @subject.noteable.lockable? }
delegate { @subject.noteable if DeclarativePolicy.has_policy?(@subject.noteable) }
condition(:is_author) { @user && @subject.author == @user }
condition(:for_merge_request, scope: :subject) { @subject.for_merge_request? }
condition(:is_noteable_author) { @user && @subject.noteable.author_id == @user.id }
condition(:editable, scope: :subject) { @subject.editable? }
rule { ~editable | anonymous }.prevent :edit_note
rule { is_author | admin }.enable :edit_note
rule { can?(:master_access) }.enable :edit_note
rule { ~editable }.prevent :admin_note
rule { is_author }.policy do
enable :read_note
enable :update_note
enable :admin_note
enable :resolve_note
end
rule { for_merge_request & is_noteable_author }.policy do
rule { is_noteable_author }.policy do
enable :resolve_note
end
end
......@@ -25,4 +25,6 @@ class PersonalSnippetPolicy < BasePolicy
end
rule { anonymous }.prevent :comment_personal_snippet
rule { can?(:comment_personal_snippet) }.enable :award_emoji
end
class ProjectPolicy < BasePolicy
def self.create_read_update_admin(name)
[
:"create_#{name}",
:"read_#{name}",
:"update_#{name}",
:"admin_#{name}"
]
end
extend ClassMethods
READONLY_FEATURES_WHEN_ARCHIVED = %i[
issue
list
merge_request
label
milestone
project_snippet
wiki
note
pipeline
pipeline_schedule
build
trigger
environment
deployment
commit_status
container_image
pages
cluster
].freeze
desc "User is a project owner"
condition :owner do
......@@ -15,7 +29,7 @@ class ProjectPolicy < BasePolicy
end
desc "Project has public builds enabled"
condition(:public_builds, scope: :subject) { project.public_builds? }
condition(:public_builds, scope: :subject, score: 0) { project.public_builds? }
# For guest access we use #team_member? so we can use
# project.members, which gets cached in subject scope.
......@@ -35,7 +49,7 @@ class ProjectPolicy < BasePolicy
condition(:master) { team_access_level >= Gitlab::Access::MASTER }
desc "Project is public"
condition(:public_project, scope: :subject) { project.public? }
condition(:public_project, scope: :subject, score: 0) { project.public? }
desc "Project is visible to internal users"
condition(:internal_access) do
......@@ -46,7 +60,7 @@ class ProjectPolicy < BasePolicy
condition(:group_member, scope: :subject) { project_group_member? }
desc "Project is archived"
condition(:archived, scope: :subject) { project.archived? }
condition(:archived, scope: :subject, score: 0) { project.archived? }
condition(:default_issues_tracker, scope: :subject) { project.default_issues_tracker? }
......@@ -56,10 +70,10 @@ class ProjectPolicy < BasePolicy
end
desc "Project has an external wiki"
condition(:has_external_wiki, scope: :subject) { project.has_external_wiki? }
condition(:has_external_wiki, scope: :subject, score: 0) { project.has_external_wiki? }
desc "Project has request access enabled"
condition(:request_access_enabled, scope: :subject) { project.request_access_enabled }
condition(:request_access_enabled, scope: :subject, score: 0) { project.request_access_enabled }
desc "Has merge requests allowing pushes to user"
condition(:has_merge_requests_allowing_pushes, scope: :subject) do
......@@ -126,6 +140,7 @@ class ProjectPolicy < BasePolicy
rule { can?(:guest_access) }.policy do
enable :read_project
enable :create_merge_request_in
enable :read_board
enable :read_list
enable :read_wiki
......@@ -140,6 +155,7 @@ class ProjectPolicy < BasePolicy
enable :create_note
enable :upload_file
enable :read_cycle_analytics
enable :award_emoji
end
# These abilities are not allowed to admins that are not members of the project,
......@@ -197,7 +213,7 @@ class ProjectPolicy < BasePolicy
enable :create_pipeline
enable :update_pipeline
enable :create_pipeline_schedule
enable :create_merge_request
enable :create_merge_request_from
enable :create_wiki
enable :push_code
enable :resolve_note
......@@ -208,7 +224,7 @@ class ProjectPolicy < BasePolicy
end
rule { can?(:master_access) }.policy do
enable :delete_protected_branch
enable :push_to_delete_protected_branch
enable :update_project_snippet
enable :update_environment
enable :update_deployment
......@@ -231,37 +247,50 @@ class ProjectPolicy < BasePolicy
end
rule { archived }.policy do
prevent :create_merge_request
prevent :push_code
prevent :delete_protected_branch
prevent :update_merge_request
prevent :admin_merge_request
prevent :push_to_delete_protected_branch
prevent :request_access
prevent :upload_file
prevent :resolve_note
prevent :create_merge_request_from
prevent :create_merge_request_in
prevent :award_emoji
READONLY_FEATURES_WHEN_ARCHIVED.each do |feature|
prevent(*create_update_admin_destroy(feature))
end
end
rule { issues_disabled }.policy do
prevent(*create_read_update_admin_destroy(:issue))
end
rule { merge_requests_disabled | repository_disabled }.policy do
prevent(*create_read_update_admin(:merge_request))
prevent :create_merge_request_in
prevent :create_merge_request_from
prevent(*create_read_update_admin_destroy(:merge_request))
end
rule { issues_disabled & merge_requests_disabled }.policy do
prevent(*create_read_update_admin(:label))
prevent(*create_read_update_admin(:milestone))
prevent(*create_read_update_admin_destroy(:label))
prevent(*create_read_update_admin_destroy(:milestone))
end
rule { snippets_disabled }.policy do
prevent(*create_read_update_admin(:project_snippet))
prevent(*create_read_update_admin_destroy(:project_snippet))
end
rule { wiki_disabled & ~has_external_wiki }.policy do
prevent(*create_read_update_admin(:wiki))
prevent(*create_read_update_admin_destroy(:wiki))
prevent(:download_wiki_code)
end
rule { builds_disabled | repository_disabled }.policy do
prevent(*create_read_update_admin(:build))
prevent(*(create_read_update_admin(:pipeline) - [:read_pipeline]))
prevent(*create_read_update_admin(:pipeline_schedule))
prevent(*create_read_update_admin(:environment))
prevent(*create_read_update_admin(:deployment))
prevent(*create_update_admin_destroy(:pipeline))
prevent(*create_read_update_admin_destroy(:build))
prevent(*create_read_update_admin_destroy(:pipeline_schedule))
prevent(*create_read_update_admin_destroy(:environment))
prevent(*create_read_update_admin_destroy(:deployment))
end
rule { repository_disabled }.policy do
......@@ -272,7 +301,7 @@ class ProjectPolicy < BasePolicy
end
rule { container_registry_disabled }.policy do
prevent(*create_read_update_admin(:container_image))
prevent(*create_read_update_admin_destroy(:container_image))
end
rule { anonymous & ~public_project }.prevent_all
......@@ -314,13 +343,6 @@ class ProjectPolicy < BasePolicy
enable :read_pipeline_schedule
end
rule { issues_disabled }.policy do
prevent :create_issue
prevent :update_issue
prevent :admin_issue
prevent :read_issue
end
# These rules are included to allow maintainers of projects to push to certain
# to run pipelines for the branches they have access to.
rule { can?(:public_access) & has_merge_requests_allowing_pushes }.policy do
......
class ProjectPolicy
module ClassMethods
def create_read_update_admin_destroy(name)
[
:"read_#{name}",
*create_update_admin_destroy(name)
]
end
def create_update_admin_destroy(name)
[
:"create_#{name}",
:"update_#{name}",
:"admin_#{name}",
:"destroy_#{name}"
]
end
end
end
......@@ -3,6 +3,7 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
include GitlabRoutingHelper
include MarkupHelper
include TreeHelper
include ChecksCollaboration
include Gitlab::Utils::StrongMemoize
presents :merge_request
......@@ -152,11 +153,11 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
end
def can_revert_on_current_merge_request?
user_can_collaborate_with_project? && cached_can_be_reverted?
can_collaborate_with_project?(project) && cached_can_be_reverted?
end
def can_cherry_pick_on_current_merge_request?
user_can_collaborate_with_project? && can_be_cherry_picked?
can_collaborate_with_project?(project) && can_be_cherry_picked?
end
def can_push_to_source_branch?
......@@ -195,12 +196,6 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
end.sort.to_sentence
end
def user_can_collaborate_with_project?
can?(current_user, :push_code, project) ||
(current_user && current_user.already_forked?(project)) ||
can_push_to_source_branch?
end
def user_can_fork_project?
can?(current_user, :fork_project, project)
end
......
......@@ -29,6 +29,10 @@ class IssueEntity < IssuableEntity
expose :can_update do |issue|
can?(request.current_user, :update_issue, issue)
end
expose :can_award_emoji do |issue|
can?(request.current_user, :award_emoji, issue)
end
end
expose :create_note_path do |issue|
......