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.`; ...@@ -317,10 +317,10 @@ Please check your network connection and try again.`;
<note-signed-out-widget v-if="!isLoggedIn" /> <note-signed-out-widget v-if="!isLoggedIn" />
<discussion-locked-widget <discussion-locked-widget
issuable-type="issue" issuable-type="issue"
v-else-if="!canCreateNote" v-else-if="isLocked(getNoteableData) && !canCreateNote"
/> />
<ul <ul
v-else v-else-if="canCreateNote"
class="notes notes-form timeline"> class="notes notes-form timeline">
<li class="timeline-entry"> <li class="timeline-entry">
<div class="timeline-entry-inner"> <div class="timeline-entry-inner">
......
...@@ -40,6 +40,10 @@ export default { ...@@ -40,6 +40,10 @@ export default {
type: Boolean, type: Boolean,
required: true, required: true,
}, },
canAwardEmoji: {
type: Boolean,
required: true,
},
canDelete: { canDelete: {
type: Boolean, type: Boolean,
required: true, required: true,
...@@ -74,9 +78,6 @@ export default { ...@@ -74,9 +78,6 @@ export default {
shouldShowActionsDropdown() { shouldShowActionsDropdown() {
return this.currentUserId && (this.canEdit || this.canReportAsAbuse); return this.currentUserId && (this.canEdit || this.canReportAsAbuse);
}, },
canAddAwardEmoji() {
return this.currentUserId;
},
isAuthoredByCurrentUser() { isAuthoredByCurrentUser() {
return this.authorId === this.currentUserId; return this.authorId === this.currentUserId;
}, },
...@@ -149,7 +150,7 @@ export default { ...@@ -149,7 +150,7 @@ export default {
</button> </button>
</div> </div>
<div <div
v-if="canAddAwardEmoji" v-if="canAwardEmoji"
class="note-actions-item"> class="note-actions-item">
<a <a
v-tooltip v-tooltip
......
...@@ -28,6 +28,10 @@ export default { ...@@ -28,6 +28,10 @@ export default {
type: Number, type: Number,
required: true, required: true,
}, },
canAwardEmoji: {
type: Boolean,
required: true,
},
}, },
computed: { computed: {
...mapGetters(['getUserData']), ...mapGetters(['getUserData']),
...@@ -67,9 +71,6 @@ export default { ...@@ -67,9 +71,6 @@ export default {
isAuthoredByMe() { isAuthoredByMe() {
return this.noteAuthorId === this.getUserData.id; return this.noteAuthorId === this.getUserData.id;
}, },
isLoggedIn() {
return this.getUserData.id;
},
}, },
created() { created() {
this.emojiSmiling = emojiSmiling; this.emojiSmiling = emojiSmiling;
...@@ -156,7 +157,7 @@ export default { ...@@ -156,7 +157,7 @@ export default {
return title; return title;
}, },
handleAward(awardName) { handleAward(awardName) {
if (!this.isLoggedIn) { if (!this.canAwardEmoji) {
return; return;
} }
...@@ -208,7 +209,7 @@ export default { ...@@ -208,7 +209,7 @@ export default {
</span> </span>
</button> </button>
<div <div
v-if="isLoggedIn" v-if="canAwardEmoji"
class="award-menu-holder"> class="award-menu-holder">
<button <button
v-tooltip v-tooltip
......
...@@ -112,6 +112,7 @@ export default { ...@@ -112,6 +112,7 @@ export default {
:note-author-id="note.author.id" :note-author-id="note.author.id"
:awards="note.award_emoji" :awards="note.award_emoji"
:toggle-award-path="note.toggle_award_path" :toggle-award-path="note.toggle_award_path"
:can-award-emoji="note.current_user.can_award_emoji"
/> />
<note-attachment <note-attachment
v-if="note.attachment" v-if="note.attachment"
......
...@@ -177,6 +177,7 @@ export default { ...@@ -177,6 +177,7 @@ export default {
:note-id="note.id" :note-id="note.id"
:access-level="note.human_access" :access-level="note.human_access"
:can-edit="note.current_user.can_edit" :can-edit="note.current_user.can_edit"
:can-award-emoji="note.current_user.can_award_emoji"
:can-delete="note.current_user.can_edit" :can-delete="note.current_user.can_edit"
:can-report-as-abuse="canReportAsAbuse" :can-report-as-abuse="canReportAsAbuse"
:report-abuse-path="note.report_abuse_path" :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 class Projects::ApplicationController < ApplicationController
include RoutableActions include RoutableActions
include ChecksCollaboration
skip_before_action :authenticate_user! skip_before_action :authenticate_user!
before_action :project before_action :project
...@@ -31,14 +32,6 @@ class Projects::ApplicationController < ApplicationController ...@@ -31,14 +32,6 @@ class Projects::ApplicationController < ApplicationController
@repository ||= project.repository @repository ||= project.repository
end 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) def authorize_action!(action)
unless can?(current_user, action, project) unless can?(current_user, action, project)
return access_denied! return access_denied!
...@@ -91,9 +84,4 @@ class Projects::ApplicationController < ApplicationController ...@@ -91,9 +84,4 @@ class Projects::ApplicationController < ApplicationController
def check_issues_available! def check_issues_available!
return render_404 unless @project.feature_available?(:issues, current_user) return render_404 unless @project.feature_available?(:issues, current_user)
end end
def user_access(project)
@user_access ||= {}
@user_access[project] ||= Gitlab::UserAccess.new(current_user, project: project)
end
end end
...@@ -20,7 +20,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -20,7 +20,7 @@ class Projects::IssuesController < Projects::ApplicationController
before_action :authorize_update_issuable!, only: [:edit, :update, :move] before_action :authorize_update_issuable!, only: [:edit, :update, :move]
# Allow create a new branch and empty WIP merge request from current issue # 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 respond_to :html
......
...@@ -5,7 +5,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap ...@@ -5,7 +5,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
skip_before_action :merge_request skip_before_action :merge_request
before_action :whitelist_query_limiting, only: [:create] 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 :apply_diff_view_cookie!, only: [:diffs, :diff_for_path]
before_action :build_merge_request, except: [:create] before_action :build_merge_request, except: [:create]
......
...@@ -12,6 +12,7 @@ class MergeRequestTargetProjectFinder ...@@ -12,6 +12,7 @@ class MergeRequestTargetProjectFinder
if @source_project.fork_network if @source_project.fork_network
@source_project.fork_network.projects @source_project.fork_network.projects
.public_or_visible_to_user(current_user) .public_or_visible_to_user(current_user)
.non_archived
.with_feature_available_for_user(:merge_requests, current_user) .with_feature_available_for_user(:merge_requests, current_user)
else else
Project.where(id: source_project) Project.where(id: source_project)
......
...@@ -59,7 +59,7 @@ module BlobHelper ...@@ -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' } 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) elsif can_modify_blob?(blob, project, ref)
button_tag label, class: "#{common_classes}", 'data-target' => "#modal-#{modal_type}-blob", 'data-toggle' => 'modal' 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) edit_fork_button_tag(common_classes, project, label, edit_modify_file_fork_params(action), action)
end end
end end
...@@ -280,7 +280,7 @@ module BlobHelper ...@@ -280,7 +280,7 @@ module BlobHelper
options << link_to("submit an issue", new_project_issue_path(project)) options << link_to("submit an issue", new_project_issue_path(project))
end 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 if merge_project
options << link_to("create a merge request", project_new_merge_request_path(project)) options << link_to("create a merge request", project_new_merge_request_path(project))
end end
...@@ -334,7 +334,7 @@ module BlobHelper ...@@ -334,7 +334,7 @@ module BlobHelper
# Web IDE (Beta) requires the user to have this feature enabled # Web IDE (Beta) requires the user to have this feature enabled
elsif !current_user || (current_user && can_modify_blob?(blob, project, ref)) elsif !current_user || (current_user && can_modify_blob?(blob, project, ref))
edit_link_tag(text, edit_path, common_classes) 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)) edit_fork_button_tag(common_classes, project, text, edit_blob_fork_params(edit_path))
end end
end end
......
...@@ -163,7 +163,7 @@ module CommitsHelper ...@@ -163,7 +163,7 @@ module CommitsHelper
tooltip = "#{action.capitalize} this #{commit.change_type_title(current_user)} in a new merge request" if has_tooltip 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? 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}" 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) elsif can?(current_user, :fork_project, @project)
continue_params = { continue_params = {
......
...@@ -3,7 +3,7 @@ module CompareHelper ...@@ -3,7 +3,7 @@ module CompareHelper
from.present? && from.present? &&
to.present? && to.present? &&
from != to && 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?(from) &&
project.repository.branch_exists?(to) project.repository.branch_exists?(to)
end end
......
...@@ -82,8 +82,8 @@ module IssuesHelper ...@@ -82,8 +82,8 @@ module IssuesHelper
names.to_sentence names.to_sentence
end end
def award_state_class(awards, current_user) def award_state_class(awardable, awards, current_user)
if !current_user if !can?(current_user, :award_emoji, awardable)
"disabled" "disabled"
elsif current_user && awards.find { |a| a.user_id == current_user.id } elsif current_user && awards.find { |a| a.user_id == current_user.id }
"active" "active"
...@@ -126,6 +126,17 @@ module IssuesHelper ...@@ -126,6 +126,17 @@ module IssuesHelper
link_to link_text, path link_to link_text, path
end 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 # Required for Banzai::Filter::IssueReferenceFilter
module_function :url_for_issue module_function :url_for_issue
module_function :url_for_internal_issue module_function :url_for_internal_issue
......
...@@ -138,6 +138,18 @@ module MergeRequestsHelper ...@@ -138,6 +138,18 @@ module MergeRequestsHelper
end end
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) def merge_params_ee(merge_request)
{} {}
end end
......
...@@ -6,10 +6,6 @@ module NotesHelper ...@@ -6,10 +6,6 @@ module NotesHelper
end end
end end
def note_editable?(note)
Ability.can_edit_note?(current_user, note)
end
def note_supports_quick_actions?(note) def note_supports_quick_actions?(note)
Notes::QuickActionsService.supported?(note) Notes::QuickActionsService.supported?(note)
end end
......
...@@ -46,10 +46,6 @@ class Ability ...@@ -46,10 +46,6 @@ class Ability
end end
end end
def can_edit_note?(user, note)
allowed?(user, :edit_note, note)
end
def allowed?(user, action, subject = :global, opts = {}) def allowed?(user, action, subject = :global, opts = {})
if subject.is_a?(Hash) if subject.is_a?(Hash)
opts, subject = subject, :global opts, subject = subject, :global
......
...@@ -79,11 +79,7 @@ module Awardable ...@@ -79,11 +79,7 @@ module Awardable
end end
def user_can_award?(current_user, name) def user_can_award?(current_user, name)
if user_authored?(current_user) awardable_by_user?(current_user, name) && Ability.allowed?(current_user, :award_emoji, self)
!awardable_votes?(normalize_name(name))
else
true
end
end end
def user_authored?(current_user) def user_authored?(current_user)
...@@ -119,4 +115,12 @@ module Awardable ...@@ -119,4 +115,12 @@ module Awardable
def normalize_name(name) def normalize_name(name)
Gitlab::Emoji.normalize_emoji_name(name) Gitlab::Emoji.normalize_emoji_name(name)
end end
def awardable_by_user?(current_user, name)
if user_authored?(current_user)
!awardable_votes?(normalize_name(name))
else
true
end
end
end end
...@@ -11,7 +11,7 @@ module Ci ...@@ -11,7 +11,7 @@ module Ci
end end
condition(:owner_of_job) do condition(:owner_of_job) do
can?(:developer_access) && @subject.triggered_by?(@user) @subject.triggered_by?(@user)
end end
rule { protected_ref }.policy do rule { protected_ref }.policy do
...@@ -19,6 +19,6 @@ module Ci ...@@ -19,6 +19,6 @@ module Ci
prevent :erase_build prevent :erase_build
end 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
end end
...@@ -7,23 +7,17 @@ module Ci ...@@ -7,23 +7,17 @@ module Ci
end end
condition(:owner_of_schedule) do condition(:owner_of_schedule) do
can?(:developer_access) && pipeline_schedule.owned_by?(@user) pipeline_schedule.owned_by?(@user)
end end
condition(:non_owner_of_schedule) do rule { can?(:create_pipeline) }.enable :play_pipeline_schedule
!pipeline_schedule.owned_by?(@user)
end
rule { can?(:developer_access) }.policy do
enable :play_pipeline_schedule
end
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 :update_pipeline_schedule
enable :admin_pipeline_schedule enable :admin_pipeline_schedule
end 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 enable :take_ownership_pipeline_schedule
end end
......
...@@ -18,9 +18,7 @@ class IssuablePolicy < BasePolicy ...@@ -18,9 +18,7 @@ class IssuablePolicy < BasePolicy
rule { locked & ~is_project_member }.policy do rule { locked & ~is_project_member }.policy do
prevent :create_note prevent :create_note
prevent :update_note
prevent :admin_note prevent :admin_note
prevent :resolve_note prevent :resolve_note
prevent :edit_note
end end
end end
class NotePolicy < BasePolicy class NotePolicy < BasePolicy
delegate { @subject.project } 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(: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(:is_noteable_author) { @user && @subject.noteable.author_id == @user.id }
condition(:editable, scope: :subject) { @subject.editable? } condition(:editable, scope: :subject) { @subject.editable? }
rule { ~editable | anonymous }.prevent :edit_note rule { ~editable }.prevent :admin_note
rule { is_author | admin }.enable :edit_note
rule { can?(:master_access) }.enable :edit_note
rule { is_author }.policy do rule { is_author }.policy do
enable :read_note enable :read_note
enable :update_note
enable :admin_note enable :admin_note
enable :resolve_note enable :resolve_note
end end
rule { for_merge_request & is_noteable_author }.policy do rule { is_noteable_author }.policy do
enable :resolve_note enable :resolve_note
end end
end end
...@@ -25,4 +25,6 @@ class PersonalSnippetPolicy < BasePolicy ...@@ -25,4 +25,6 @@ class PersonalSnippetPolicy < BasePolicy
end end
rule { anonymous }.prevent :comment_personal_snippet rule { anonymous }.prevent :comment_personal_snippet
rule { can?(:comment_personal_snippet) }.enable :award_emoji
end end
class ProjectPolicy < BasePolicy class ProjectPolicy < BasePolicy
def self.create_read_update_admin(name) extend ClassMethods
[
:"create_#{name}", READONLY_FEATURES_WHEN_ARCHIVED = %i[
:"read_#{name}", issue
:"update_#{name}", list
:"admin_#{name}" merge_request
] label
end 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" desc "User is a project owner"
condition :owner do condition :owner do
...@@ -15,7 +29,7 @@ class ProjectPolicy < BasePolicy ...@@ -15,7 +29,7 @@ class ProjectPolicy < BasePolicy
end end
desc "Project has public builds enabled" 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 # For guest access we use #team_member? so we can use
# project.members, which gets cached in subject scope. # project.members, which gets cached in subject scope.
...@@ -35,7 +49,7 @@ class ProjectPolicy < BasePolicy ...@@ -35,7 +49,7 @@ class ProjectPolicy < BasePolicy
condition(:master) { team_access_level >= Gitlab::Access::MASTER } condition(:master) { team_access_level >= Gitlab::Access::MASTER }
desc "Project is public" 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" desc "Project is visible to internal users"
condition(:internal_access) do condition(:internal_access) do
...@@ -46,7 +60,7 @@ class ProjectPolicy < BasePolicy ...@@ -46,7 +60,7 @@ class ProjectPolicy < BasePolicy
condition(:group_member, scope: :subject) { project_group_member? } condition(:group_member, scope: :subject) { project_group_member? }
desc "Project is archived" 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? } condition(:default_issues_tracker, scope: :subject) { project.default_issues_tracker? }
...@@ -56,10 +70,10 @@ class ProjectPolicy < BasePolicy ...@@ -56,10 +70,10 @@ class ProjectPolicy < BasePolicy
end end
desc "Project has an external wiki" 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" 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" desc "Has merge requests allowing pushes to user"
condition(:has_merge_requests_allowing_pushes, scope: :subject) do condition(:has_merge_requests_allowing_pushes, scope: :subject) do
...@@ -126,6 +140,7 @@ class ProjectPolicy < BasePolicy ...@@ -126,6 +140,7 @@ class ProjectPolicy < BasePolicy
rule { can?(:guest_access) }.policy do rule { can?(:guest_access) }.policy do
enable :read_project enable :read_project
enable :create_merge_request_in
enable :read_board enable :read_board
enable :read_list enable :read_list
enable :read_wiki enable :read_wiki
...@@ -140,6 +155,7 @@ class ProjectPolicy < BasePolicy ...@@ -140,6 +155,7 @@ class ProjectPolicy < BasePolicy
enable :create_note enable :create_note
enable :upload_file enable :upload_file
enable :read_cycle_analytics enable :read_cycle_analytics
enable :award_emoji
end end
# These abilities are not allowed to admins that are not members of the project, # These abilities are not allowed to admins that are not members of the project,
...@@ -197,7 +213,7 @@ class ProjectPolicy < BasePolicy ...@@ -197,7 +213,7 @@ class ProjectPolicy < BasePolicy
enable :create_pipeline enable :create_pipeline
enable :update_pipeline enable :update_pipeline
enable :create_pipeline_schedule enable :create_pipeline_schedule
enable :create_merge_request enable :create_merge_request_from
enable :create_wiki enable :create_wiki
enable :push_code enable :push_code
enable :resolve_note enable :resolve_note
...@@ -208,7 +224,7 @@ class ProjectPolicy < BasePolicy ...@@ -208,7 +224,7 @@ class ProjectPolicy < BasePolicy
end end
rule { can?(:master_access) }.policy do rule { can?(:master_access) }.policy do
enable :delete_protected_branch enable :push_to_delete_protected_branch
enable :update_project_snippet enable :update_project_snippet
enable :update_environment enable :update_environment
enable :update_deployment enable :update_deployment
...@@ -231,37 +247,50 @@ class ProjectPolicy < BasePolicy ...@@ -231,37 +247,50 @@ class ProjectPolicy < BasePolicy
end end
rule { archived }.policy do rule { archived }.policy do
prevent :create_merge_request
prevent :push_code prevent :push_code
prevent :delete_protected_branch prevent :push_to_delete_protected_branch
prevent :update_merge_request prevent :request_access
prevent :admin_merge_request 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 end
rule { merge_requests_disabled | repository_disabled }.policy do 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 end
rule { issues_disabled & merge_requests_disabled }.policy do rule { issues_disabled & merge_requests_disabled }.policy do
prevent(*create_read_update_admin(:label)) prevent(*create_read_update_admin_destroy(:label))
prevent(*create_read_update_admin(:milestone)) prevent(*create_read_update_admin_destroy(:milestone))
end end
rule { snippets_disabled }.policy do rule { snippets_disabled }.policy do
prevent(*create_read_update_admin(:project_snippet)) prevent(*create_read_update_admin_destroy(:project_snippet))
end end
rule { wiki_disabled & ~has_external_wiki }.policy do 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) prevent(:download_wiki_code)
end end
rule { builds_disabled | repository_disabled }.policy do rule { builds_disabled | repository_disabled }.policy do
prevent(*create_read_update_admin(:build)) prevent(*create_update_admin_destroy(:pipeline))
prevent(*(create_read_update_admin(:pipeline) - [:read_pipeline])) prevent(*create_read_update_admin_destroy(:build))
prevent(*create_read_update_admin(:pipeline_schedule)) prevent(*create_read_update_admin_destroy(:pipeline_schedule))
prevent(*create_read_update_admin(:environment)) prevent(*create_read_update_admin_destroy(:environment))
prevent(*create_read_update_admin(:deployment)) prevent(*create_read_update_admin_destroy(:deployment))
end end
rule { repository_disabled }.policy do rule { repository_disabled }.policy do
...@@ -272,7 +301,7 @@ class ProjectPolicy < BasePolicy ...@@ -272,7 +301,7 @@ class ProjectPolicy < BasePolicy
end end
rule { container_registry_disabled }.policy do rule { container_registry_disabled }.policy do
prevent(*create_read_update_admin(:container_image)) prevent(*create_read_update_admin_destroy(:container_image))
end end
rule { anonymous & ~public_project }.prevent_all rule { anonymous & ~public_project }.prevent_all
...@@ -314,13 +343,6 @@ class ProjectPolicy < BasePolicy ...@@ -314,13 +343,6 @@ class ProjectPolicy < BasePolicy
enable :read_pipeline_schedule enable :read_pipeline_schedule
end 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 # These rules are included to allow maintainers of projects to push to certain
# to run pipelines for the branches they have access to. # to run pipelines for the branches they have access to.
rule { can?(:public_access) & has_merge_requests_allowing_pushes }.policy do 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 ...@@ -3,6 +3,7 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
include GitlabRoutingHelper include GitlabRoutingHelper
include MarkupHelper include MarkupHelper
include TreeHelper include TreeHelper
include ChecksCollaboration
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
presents :merge_request presents :merge_request
...@@ -152,11 +153,11 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated ...@@ -152,11 +153,11 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
end end
def can_revert_on_current_merge_request? 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 end
def can_cherry_pick_on_current_merge_request? 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 end
def can_push_to_source_branch? def can_push_to_source_branch?
...@@ -195,12 +196,6 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated ...@@ -195,12 +196,6 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
end.sort.to_sentence end.sort.to_sentence
end 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? def user_can_fork_project?
can?(current_user, :fork_project, project) can?(current_user, :fork_project, project)
end end
......
...@@ -29,6 +29,10 @@ class IssueEntity < IssuableEntity ...@@ -29,6 +29,10 @@ class IssueEntity < IssuableEntity
expose :can_update do |issue| expose :can_update do |issue|
can?(request.current_user, :update_issue, issue) can?(request.current_user, :update_issue, issue)
end end
expose :can_award_emoji do |issue|
can?(request.current_user, :award_emoji, issue)
end
end end
expose :create_note_path do |issue| expose :create_note_path do |issue|
......
...@@ -15,7 +15,11 @@ class NoteEntity < API::Entities::Note ...@@ -15,7 +15,11 @@ class NoteEntity < API::Entities::Note
expose :current_user do expose :current_user do
expose :can_edit do |note| expose :can_edit do |note|
Ability.can_edit_note?(request.current_user, note) Ability.allowed?(request.current_user, :admin_note, note)
end
expose :can_award_emoji do |note|
Ability.allowed?(request.current_user, :award_emoji, note)
end end
end end
......
...@@ -71,8 +71,8 @@ module MergeRequests ...@@ -71,8 +71,8 @@ module MergeRequests
params.delete(:source_project_id) params.delete(:source_project_id)
params.delete(:target_project_id) params.delete(:target_project_id)
unless can?(current_user, :read_project, @source_project) && unless can?(current_user, :create_merge_request_from, @source_project) &&
can?(current_user, :read_project, @project) can?(current_user, :create_merge_request_in, @project)
raise Gitlab::Access::AccessDeniedError raise Gitlab::Access::AccessDeniedError
end end
......
...@@ -101,7 +101,7 @@ ...@@ -101,7 +101,7 @@
- if @project.archived? - if @project.archived?
%li %li
%span.light archived: %span.light archived:
%strong repository is read-only %strong project is read-only
%li %li
%span.light access: %span.light access:
......
...@@ -3,13 +3,13 @@ ...@@ -3,13 +3,13 @@
.awards.js-awards-block{ class: ("hidden" if !inline && grouped_emojis.empty?), data: { award_url: toggle_award_url(awardable) } } .awards.js-awards-block{ class: ("hidden" if !inline && grouped_emojis.empty?), data: { award_url: toggle_award_url(awardable) } }
- awards_sort(grouped_emojis).each do |emoji, awards| - awards_sort(grouped_emojis).each do |emoji, awards|
%button.btn.award-control.js-emoji-btn.has-tooltip{ type: "button", %button.btn.award-control.js-emoji-btn.has-tooltip{ type: "button",
class: [(award_state_class(awards, current_user)), (award_user_authored_class(emoji) if user_authored)], class: [(award_state_class(awardable, awards, current_user)), (award_user_authored_class(emoji) if user_authored)],
data: { placement: "bottom", title: award_user_list(awards, current_user) } } data: { placement: "bottom", title: award_user_list(awards, current_user) } }
= emoji_icon(emoji) = emoji_icon(emoji)
%span.award-control-text.js-counter %span.award-control-text.js-counter
= awards.count = awards.count
- if current_user - if can?(current_user, :award_emoji, awardable)
.award-menu-holder.js-award-holder .award-menu-holder.js-award-holder
%button.btn.award-control.has-tooltip.js-add-award{ type: 'button', %button.btn.award-control.has-tooltip.js-add-award{ type: 'button',
'aria-label': 'Add reaction', 'aria-label': 'Add reaction',
......
...@@ -19,8 +19,8 @@ ...@@ -19,8 +19,8 @@
%li.dropdown-bold-header GitLab %li.dropdown-bold-header GitLab
- if @project&.persisted? - if @project&.persisted?
- create_project_issue = can?(current_user, :create_issue, @project) - create_project_issue = show_new_issue_link?(@project)
- 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)
- create_project_snippet = can?(current_user, :create_project_snippet, @project) - create_project_snippet = can?(current_user, :create_project_snippet, @project)
- if create_project_issue || merge_project || create_project_snippet - if create_project_issue || merge_project || create_project_snippet
%li.dropdown-bold-header This project %li.dropdown-bold-header This project
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#{time_ago_with_tooltip(event.created_at)} #{time_ago_with_tooltip(event.created_at)}
.flex-right - if can?(current_user, :create_merge_request_in, event.project.default_merge_request_target)
= link_to new_mr_path_from_push_event(event), title: _("New merge request"), class: "btn btn-info btn-sm qa-create-merge-request" do .flex-right
#{ _('Create merge request') } = link_to new_mr_path_from_push_event(event), title: _("New merge request"), class: "btn btn-info btn-sm qa-create-merge-request" do
#{ _('Create merge request') }
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
- diverging_commit_counts = @repository.diverging_commit_counts(branch) - diverging_commit_counts = @repository.diverging_commit_counts(branch)
- number_commits_behind = diverging_commit_counts[:behind] - number_commits_behind = diverging_commit_counts[:behind]
- number_commits_ahead = diverging_commit_counts[:ahead] - number_commits_ahead = diverging_commit_counts[:ahead]
- 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)
%li{ class: "branch-item js-branch-#{branch.name}" } %li{ class: "branch-item js-branch-#{branch.name}" }
.branch-info .branch-info
.branch-title .branch-title
...@@ -61,7 +61,7 @@ ...@@ -61,7 +61,7 @@
title: s_('Branches|The default branch cannot be deleted') } title: s_('Branches|The default branch cannot be deleted') }
= icon("trash-o") = icon("trash-o")
- elsif protected_branch?(@project, branch) - elsif protected_branch?(@project, branch)
- if can?(current_user, :delete_protected_branch, @project) - if can?(current_user, :push_to_delete_protected_branch, @project)
%button{ class: "btn btn-remove remove-row js-ajax-loading-spinner has-tooltip", %button{ class: "btn btn-remove remove-row js-ajax-loading-spinner has-tooltip",
title: s_('Branches|Delete protected branch'), title: s_('Branches|Delete protected branch'),
data: { toggle: "modal", data: { toggle: "modal",
......
- if current_user - can_create_issue = show_new_issue_link?(@project)
- can_create_project_snippet = can?(current_user, :create_project_snippet, @project)
- can_push_code = can?(current_user, :push_code, @project)
- create_mr_from_new_fork = can?(current_user, :fork_project, @project) && can?(current_user, :create_merge_request_in, @project)
- merge_project = merge_request_source_project_for_project(@project)
- show_menu = can_create_issue || can_create_project_snippet || can_push_code || create_mr_from_new_fork || merge_project
- if show_menu
.project-action-button.dropdown.inline .project-action-button.dropdown.inline
%a.btn.dropdown-toggle.has-tooltip{ href: '#', title: _('Create new...'), 'data-toggle' => 'dropdown', 'data-container' => 'body', 'aria-label' => _('Create new...') } %a.btn.dropdown-toggle.has-tooltip{ href: '#', title: _('Create new...'), 'data-toggle' => 'dropdown', 'data-container' => 'body', 'aria-label' => _('Create new...') }
= icon('plus') = icon('plus')
= icon("caret-down") = icon("caret-down")
%ul.dropdown-menu.dropdown-menu-align-right.project-home-dropdown %ul.dropdown-menu.dropdown-menu-align-right.project-home-dropdown
- can_create_issue = can?(current_user, :create_issue, @project)
- merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
- can_create_project_snippet = can?(current_user, :create_project_snippet, @project)
- if can_create_issue || merge_project || can_create_project_snippet - if can_create_issue || merge_project || can_create_project_snippet
%li.dropdown-header= _('This project') %li.dropdown-header= _('This project')
...@@ -20,17 +24,17 @@ ...@@ -20,17 +24,17 @@
- if can_create_project_snippet - if can_create_project_snippet
%li= link_to _('New snippet'), new_project_snippet_path(@project) %li= link_to _('New snippet'), new_project_snippet_path(@project)
- if can?(current_user, :push_code, @project) - if can_push_code
%li.dropdown-header= _('This repository') %li.dropdown-header= _('This repository')
- if can?(current_user, :push_code, @project) - if can_push_code
%li= link_to _('New file'), project_new_blob_path(@project, @project.default_branch || 'master') %li= link_to _('New file'), project_new_blob_path(@project, @project.default_branch || 'master')
- unless @project.empty_repo? - unless @project.empty_repo?
%li= link_to _('New branch'), new_project_branch_path(@project) %li= link_to _('New branch'), new_project_branch_path(@project)
%li= link_to _('New tag'), new_project_tag_path(@project) %li= link_to _('New tag'), new_project_tag_path(@project)
- elsif current_user && current_user.already_forked?(@project) - elsif can_collaborate_with_project?(@project)
%li= link_to _('New file'), project_new_blob_path(@project, @project.default_branch || 'master') %li= link_to _('New file'), project_new_blob_path(@project, @project.default_branch || 'master')
- elsif can?(current_user, :fork_project, @project) - elsif create_mr_from_new_fork
- continue_params = { to: project_new_blob_path(@project, @project.default_branch || 'master'), - continue_params = { to: project_new_blob_path(@project, @project.default_branch || 'master'),
notice: edit_in_new_fork_notice, notice: edit_in_new_fork_notice,
notice_now: edit_in_new_fork_notice_now } notice_now: edit_in_new_fork_notice_now }
......
...@@ -7,5 +7,6 @@ ...@@ -7,5 +7,6 @@
- link_to_help_page = link_to(_('Learn more about Kubernetes'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer') - link_to_help_page = link_to(_('Learn more about Kubernetes'), help_page_path('user/project/clusters/index'), target: '_blank', rel: 'noopener noreferrer')
%p= s_('ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}').html_safe % { link_to_help_page: link_to_help_page} %p= s_('ClusterIntegration|Kubernetes clusters allow you to use review apps, deploy your applications, run your pipelines, and much more in an easy way. %{link_to_help_page}').html_safe % { link_to_help_page: link_to_help_page}
.text-center - if can?(current_user, :create_cluster, @project)
= link_to s_('ClusterIntegration|Add Kubernetes cluster'), new_project_cluster_path(@project), class: 'btn btn-success' .text-center
= link_to s_('ClusterIntegration|Add Kubernetes cluster'), new_project_cluster_path(@project), class: 'btn btn-success'
- can_collaborate = can_collaborate_with_project?(@project)
.page-content-header.js-commit-box{ 'data-commit-path' => branches_project_commit_path(@project, @commit.id) } .page-content-header.js-commit-box{ 'data-commit-path' => branches_project_commit_path(@project, @commit.id) }
.header-main-content .header-main-content
= render partial: 'signature', object: @commit.signature = render partial: 'signature', object: @commit.signature
...@@ -32,12 +34,13 @@ ...@@ -32,12 +34,13 @@
%li.visible-xs-block.visible-sm-block %li.visible-xs-block.visible-sm-block
= link_to project_tree_path(@project, @commit) do = link_to project_tree_path(@project, @commit) do
#{ _('Browse Files') } #{ _('Browse Files') }
- unless @commit.has_been_reverted?(current_user) - if can_collaborate && !@commit.has_been_reverted?(current_user)
%li.clearfix %li.clearfix
= revert_commit_link(@commit, project_commit_path(@project, @commit.id), has_tooltip: false) = revert_commit_link(@commit, project_commit_path(@project, @commit.id), has_tooltip: false)
%li.clearfix - if can_collaborate
= cherry_pick_commit_link(@commit, project_commit_path(@project, @commit.id), has_tooltip: false) %li.clearfix
- if can_collaborate_with_project? = cherry_pick_commit_link(@commit, project_commit_path(@project, @commit.id), has_tooltip: false)
- if can?(current_user, :push_code, @project)
%li.clearfix %li.clearfix
= link_to s_("CreateTag|Tag"), new_project_tag_path(@project, ref: @commit) = link_to s_("CreateTag|Tag"), new_project_tag_path(@project, ref: @commit)
%li.divider %li.divider
......
...@@ -17,6 +17,6 @@ ...@@ -17,6 +17,6 @@
.limited-width-notes .limited-width-notes
= render "shared/notes/notes_with_form", :autocomplete => true = render "shared/notes/notes_with_form", :autocomplete => true
- if can_collaborate_with_project? - if can_collaborate_with_project?(@project)
- %w(revert cherry-pick).each do |type| - %w(revert cherry-pick).each do |type|
= render "projects/commit/change", type: type, commit: @commit, title: @commit.title = render "projects/commit/change", type: type, commit: @commit, title: @commit.title
...@@ -114,17 +114,18 @@ ...@@ -114,17 +114,18 @@
Archive project Archive project
- if @project.archived? - if @project.archived?
%p %p
Unarchiving the project will mark its repository as active. The project can be committed to. Unarchiving the project will restore people's ability to make changes to it.
The repository can be committed to, and issues, comments and other entities can be created.
%strong Once active this project shows up in the search and on the dashboard. %strong Once active this project shows up in the search and on the dashboard.
= link_to 'Unarchive project', unarchive_project_path(@project), = link_to 'Unarchive project', unarchive_project_path(@project),
data: { confirm: "Are you sure that you want to unarchive this project?\nWhen this project is unarchived it is active and can be committed to again." }, data: { confirm: "Are you sure that you want to unarchive this project?" },
method: :post, class: "btn btn-success" method: :post, class: "btn btn-success"
- else - else
%p %p
Archiving the project will mark its repository as read-only. It is hidden from the dashboard and doesn't show up in searches. Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches.
%strong Archived projects cannot be committed to! %strong The repository cannot be committed to, and no issues, comments or other entities can be created.
= link_to 'Archive project', archive_project_path(@project), = link_to 'Archive project', archive_project_path(@project),
data: { confirm: "Are you sure that you want to archive this project?\nAn archived project cannot be committed to." }, data: { confirm: "Are you sure that you want to archive this project?" },
method: :post, class: "btn btn-warning" method: :post, class: "btn btn-warning"
.sub-section.rename-respository .sub-section.rename-respository
%h4.warning-title %h4.warning-title
......
...@@ -2,9 +2,10 @@ ...@@ -2,9 +2,10 @@
= icon('rss') = icon('rss')
- if @can_bulk_update - if @can_bulk_update
= button_tag "Edit issues", class: "btn btn-default append-right-10 js-bulk-update-toggle" = button_tag "Edit issues", class: "btn btn-default append-right-10 js-bulk-update-toggle"
= link_to "New issue", new_project_issue_path(@project, - if show_new_issue_link?(@project)
issue: { assignee_id: finder.assignee.try(:id), = link_to "New issue", new_project_issue_path(@project,
milestone_id: finder.milestones.first.try(:id) }), issue: { assignee_id: finder.assignee.try(:id),
class: "btn btn-new", milestone_id: finder.milestones.first.try(:id) }),
title: "New issue", class: "btn btn-new",
id: "new_issue_link" title: "New issue",
id: "new_issue_link"
- can_create_merge_request = can?(current_user, :create_merge_request, @project)
- data_action = can_create_merge_request ? 'create-mr' : 'create-branch'
- value = can_create_merge_request ? 'Create merge request' : 'Create branch'
- if can?(current_user, :push_code, @project) - if can?(current_user, :push_code, @project)
- can_create_merge_request = can?(current_user, :create_merge_request_in, @project)
- data_action = can_create_merge_request ? 'create-mr' : 'create-branch'
- value = can_create_merge_request ? 'Create merge request' : 'Create branch'
- can_create_path = can_create_branch_project_issue_path(@project, @issue) - can_create_path = can_create_branch_project_issue_path(@project, @issue)
- create_mr_path = create_merge_request_project_issue_path(@project, @issue, branch_name: @issue.to_branch_name, ref: @project.default_branch) - create_mr_path = create_merge_request_project_issue_path(@project, @issue, branch_name: @issue.to_branch_name, ref: @project.default_branch)
- create_branch_path = project_branches_path(@project, branch_name: @issue.to_branch_name, ref: @project.default_branch, issue_iid: @issue.iid) - create_branch_path = project_branches_path(@project, branch_name: @issue.to_branch_name, ref: @project.default_branch, issue_iid: @issue.iid)
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
- can_update_issue = can?(current_user, :update_issue, @issue) - can_update_issue = can?(current_user, :update_issue, @issue)
- can_report_spam = @issue.submittable_as_spam_by?(current_user) - can_report_spam = @issue.submittable_as_spam_by?(current_user)
- can_create_issue = show_new_issue_link?(@project)
.detail-page-header .detail-page-header
.detail-page-header-body .detail-page-header-body
...@@ -42,16 +43,18 @@ ...@@ -42,16 +43,18 @@
%li= link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, format: 'json'), class: "btn-reopen js-btn-issue-action #{issue_button_visibility(@issue, false)}", title: 'Reopen issue' %li= link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, format: 'json'), class: "btn-reopen js-btn-issue-action #{issue_button_visibility(@issue, false)}", title: 'Reopen issue'
- if can_report_spam - if can_report_spam
%li= link_to 'Submit as spam', mark_as_spam_project_issue_path(@project, @issue), method: :post, class: 'btn-spam', title: 'Submit as spam' %li= link_to 'Submit as spam', mark_as_spam_project_issue_path(@project, @issue), method: :post, class: 'btn-spam', title: 'Submit as spam'
- if can_update_issue || can_report_spam - if can_create_issue
%li.divider - if can_update_issue || can_report_spam
%li= link_to 'New issue', new_project_issue_path(@project), title: 'New issue', id: 'new_issue_link' %li.divider
%li= link_to 'New issue', new_project_issue_path(@project), title: 'New issue', id: 'new_issue_link'
= render 'shared/issuable/close_reopen_button', issuable: @issue, can_update: can_update_issue = render 'shared/issuable/close_reopen_button', issuable: @issue, can_update: can_update_issue
- if can_report_spam - if can_report_spam
= link_to 'Submit as spam', mark_as_spam_project_issue_path(@project, @issue), method: :post, class: 'hidden-xs hidden-sm btn btn-grouped btn-spam', title: 'Submit as spam' = link_to 'Submit as spam', mark_as_spam_project_issue_path(@project, @issue), method: :post, class: 'hidden-xs hidden-sm btn btn-grouped btn-spam', title: 'Submit as spam'
= link_to new_project_issue_path(@project), class: 'hidden-xs hidden-sm btn btn-grouped new-issue-link btn-new btn-inverted', title: 'New issue', id: 'new_issue_link' do - if can_create_issue
New issue = link_to new_project_issue_path(@project), class: 'hidden-xs hidden-sm btn btn-grouped new-issue-link btn-new btn-inverted', title: 'New issue', id: 'new_issue_link' do
New issue
.issue-details.issuable-details .issue-details.issuable-details
.detail-page-description.content-block .detail-page-description.content-block
......
- @no_container = true - @no_container = true
- @can_bulk_update = can?(current_user, :admin_merge_request, @project) - @can_bulk_update = can?(current_user, :admin_merge_request, @project)
- 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)
- new_merge_request_path = project_new_merge_request_path(merge_project) if merge_project - new_merge_request_path = project_new_merge_request_path(merge_project) if merge_project
- page_title "Merge Requests" - page_title "Merge Requests"
......
...@@ -36,7 +36,7 @@ ...@@ -36,7 +36,7 @@
%template{ 'v-else' => '' } %template{ 'v-else' => '' }
= render 'shared/icons/icon_resolve_discussion.svg' = render 'shared/icons/icon_resolve_discussion.svg'
- if current_user - if can?(current_user, :award_emoji, note)
- if note.emoji_awardable? - if note.emoji_awardable?
- user_authored = note.user_authored?(current_user) - user_authored = note.user_authored?(current_user)
.note-actions-item .note-actions-item
......
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
.text-warning.center.prepend-top-20 .text-warning.center.prepend-top-20
%p %p
= icon("exclamation-triangle fw") = icon("exclamation-triangle fw")
#{ _('Archived project! Repository is read-only') } #{ _('Archived project! Repository and other project resources are read-only') }
- view_path = @project.default_view - view_path = @project.default_view
......
...@@ -31,6 +31,6 @@ ...@@ -31,6 +31,6 @@
= link_to edit_project_tag_release_path(@project, tag.name), class: 'btn has-tooltip', title: s_('TagsPage|Edit release notes'), data: { container: "body" } do = link_to edit_project_tag_release_path(@project, tag.name), class: 'btn has-tooltip', title: s_('TagsPage|Edit release notes'), data: { container: "body" } do
= icon("pencil") = icon("pencil")
- if can?(current_user, :admin_project, @project) - if can?(current_user, :admin_project, @project)
= 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 = 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") = icon("trash-o")
...@@ -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, :admin_project, @project) - if can?(current_user, :push_code, @project) && can?(current_user, :admin_project, @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
......
- can_collaborate = can_collaborate_with_project?(@project)
- can_create_mr_from_fork = can?(current_user, :fork_project, @project) && can?(current_user, :create_merge_request_in, @project)
.tree-ref-container .tree-ref-container
.tree-ref-holder .tree-ref-holder
= render 'shared/ref_switcher', destination: 'tree', path: @path, show_create: true = render 'shared/ref_switcher', destination: 'tree', path: @path, show_create: true
...@@ -15,7 +18,7 @@ ...@@ -15,7 +18,7 @@
%li %li
= link_to truncate(title, length: 40), project_tree_path(@project, tree_join(@ref, path)) = link_to truncate(title, length: 40), project_tree_path(@project, tree_join(@ref, path))
- if current_user - if can_collaborate || can_create_mr_from_fork
%li %li
%a.btn.add-to-tree{ addtotree_toggle_attributes } %a.btn.add-to-tree{ addtotree_toggle_attributes }
= sprite_icon('plus', size: 16, css_class: 'pull-left') = sprite_icon('plus', size: 16, css_class: 'pull-left')
...@@ -35,7 +38,7 @@ ...@@ -35,7 +38,7 @@
%li %li
= link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal' } do = link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal' } do
#{ _('New directory') } #{ _('New directory') }
- elsif can?(current_user, :fork_project, @project) - elsif can?(current_user, :fork_project, @project) && can?(current_user, :create_merge_request_in, @project)
%li %li
- continue_params = { to: project_new_blob_path(@project, @id), - continue_params = { to: project_new_blob_path(@project, @id),
notice: edit_in_new_fork_notice, notice: edit_in_new_fork_notice,
...@@ -61,23 +64,25 @@ ...@@ -61,23 +64,25 @@
= link_to fork_path, method: :post do = link_to fork_path, method: :post do
#{ _('New directory') } #{ _('New directory') }
%li.divider - if can?(current_user, :push_code, @project)
%li.dropdown-header %li.divider
#{ _('This repository') } %li.dropdown-header
%li #{ _('This repository') }
= link_to new_project_branch_path(@project) do %li
#{ _('New branch') } = link_to new_project_branch_path(@project) do
%li #{ _('New branch') }
= link_to new_project_tag_path(@project) do %li
#{ _('New tag') } = link_to new_project_tag_path(@project) do
#{ _('New tag') }
.tree-controls .tree-controls
= link_to s_('Commits|History'), project_commits_path(@project, @id), class: 'btn' = link_to s_('Commits|History'), project_commits_path(@project, @id), class: 'btn'
= render 'projects/find_file_link' = render 'projects/find_file_link'
= succeed " " do - if can_collaborate
= link_to ide_edit_path(@project, @id, ""), class: 'btn btn-default' do = succeed " " do
= _('Web IDE') = link_to ide_edit_path(@project, @id, ""), class: 'btn btn-default' do
= _('Web IDE')
= render 'projects/buttons/download', project: @project, ref: @ref = render 'projects/buttons/download', project: @project, ref: @ref
...@@ -47,20 +47,20 @@ ...@@ -47,20 +47,20 @@
class: 'text-danger' class: 'text-danger'
.pull-right.hidden-xs.hidden-sm .pull-right.hidden-xs.hidden-sm
- if label.is_a?(ProjectLabel) && label.project.group && can?(current_user, :admin_label, label.project.group)
%button.js-promote-project-label-button.btn.btn-transparent.btn-action.has-tooltip{ title: _('Promote to Group Label'),
disabled: true,
type: 'button',
data: { url: promote_project_label_path(label.project, label),
label_title: label.title,
label_color: label.color,
label_text_color: label.text_color,
group_name: label.project.group.name,
target: '#promote-label-modal',
container: 'body',
toggle: 'modal' } }
= sprite_icon('level-up')
- if can?(current_user, :admin_label, label) - if can?(current_user, :admin_label, label)
- if label.is_a?(ProjectLabel) && label.project.group && can?(current_user, :admin_label, label.project.group)
%button.js-promote-project-label-button.btn.btn-transparent.btn-action.has-tooltip{ title: _('Promote to Group Label'),
disabled: true,
type: 'button',
data: { url: promote_project_label_path(label.project, label),
label_title: label.title,
label_color: label.color,
label_text_color: label.text_color,
group_name: label.project.group.name,
target: '#promote-label-modal',
container: 'body',
toggle: 'modal' } }
= sprite_icon('level-up')
= link_to edit_label_path(label), title: "Edit", class: 'btn btn-transparent btn-action', data: {toggle: "tooltip"} do = link_to edit_label_path(label), title: "Edit", class: 'btn btn-transparent btn-action', data: {toggle: "tooltip"} do
%span.sr-only Edit %span.sr-only Edit
= sprite_icon('pencil') = sprite_icon('pencil')
......
...@@ -72,7 +72,7 @@ ...@@ -72,7 +72,7 @@
.title.hide-collapsed .title.hide-collapsed
Issues Issues
%span.badge= milestone.issues_visible_to_user(current_user).count %span.badge= milestone.issues_visible_to_user(current_user).count
- if project && can?(current_user, :create_issue, project) - if show_new_issue_link?(project)
= link_to new_project_issue_path(project, issue: { milestone_id: milestone.id }), class: "pull-right", title: "New Issue" do = link_to new_project_issue_path(project, issue: { milestone_id: milestone.id }), class: "pull-right", title: "New Issue" do
New issue New issue
.value.hide-collapsed.bold .value.hide-collapsed.bold
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
- return if note.cross_reference_not_visible_for?(current_user) - return if note.cross_reference_not_visible_for?(current_user)
- show_image_comment_badge = local_assigns.fetch(:show_image_comment_badge, false) - show_image_comment_badge = local_assigns.fetch(:show_image_comment_badge, false)
- note_editable = note_editable?(note) - note_editable = can?(current_user, :admin_note, note)
- note_counter = local_assigns.fetch(:note_counter, 0) - note_counter = local_assigns.fetch(:note_counter, 0)
%li.timeline-entry{ id: dom_id(note), %li.timeline-entry{ id: dom_id(note),
......
...@@ -4,7 +4,7 @@ Gitlab::Seeder.quiet do ...@@ -4,7 +4,7 @@ Gitlab::Seeder.quiet do
# Limit the number of merge requests per project to avoid long seeds # Limit the number of merge requests per project to avoid long seeds
MAX_NUM_MERGE_REQUESTS = 10 MAX_NUM_MERGE_REQUESTS = 10
Project.all.reject(&:empty_repo?).each do |project| Project.non_archived.with_merge_requests_enabled.reject(&:empty_repo?).each do |project|
branches = project.repository.branch_names.sample(MAX_NUM_MERGE_REQUESTS * 2) branches = project.repository.branch_names.sample(MAX_NUM_MERGE_REQUESTS * 2)
branches.each do |branch_name| branches.each do |branch_name|
...@@ -21,7 +21,11 @@ Gitlab::Seeder.quiet do ...@@ -21,7 +21,11 @@ Gitlab::Seeder.quiet do
assignee: project.team.users.sample assignee: project.team.users.sample
} }
MergeRequests::CreateService.new(project, project.team.users.sample, params).execute # Only create MRs with users that are allowed to create MRs
developer = project.team.developers.sample
break unless developer
MergeRequests::CreateService.new(project, developer, params).execute
print '.' print '.'
end end
end end
......
...@@ -57,15 +57,20 @@ Here you can run housekeeping, archive, rename, transfer, or remove a project. ...@@ -57,15 +57,20 @@ Here you can run housekeeping, archive, rename, transfer, or remove a project.
NOTE: **Note:** NOTE: **Note:**
Only project Owners and Admin users have the [permissions] to archive a project. Only project Owners and Admin users have the [permissions] to archive a project.
An archived project will be hidden by default in the project listings. Archiving a project makes it read-only for all users and indicates that it is
no longer actively maintained. Projects that have been archived can also be
unarchived.
When a project is archived, the repository, issues, merge requests and all
other features are read-only. Archived projects are also hidden
in project listings.
To archive a project:
1. Navigate to your project's **Settings > General > Advanced settings**. 1. Navigate to your project's **Settings > General > Advanced settings**.
1. Under "Archive project", hit the **Archive project** button. 1. In the Archive project section, click the **Archive project** button.
1. Confirm the action when asked to. 1. Confirm the action when asked to.
An archived project can be fully restored and will therefore retain its
repository and all associated resources whilst in an archived state.
#### Renaming a repository #### Renaming a repository
NOTE: **Note:** NOTE: **Note:**
......
...@@ -189,7 +189,7 @@ module API ...@@ -189,7 +189,7 @@ module API
post ":id/merge_requests" do post ":id/merge_requests" do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42316') Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42316')
authorize! :create_merge_request, user_project authorize! :create_merge_request_from, user_project
mr_params = declared_params(include_missing: false) mr_params = declared_params(include_missing: false)
mr_params[:force_remove_source_branch] = mr_params.delete(:remove_source_branch) mr_params[:force_remove_source_branch] = mr_params.delete(:remove_source_branch)
......
...@@ -93,7 +93,7 @@ module API ...@@ -93,7 +93,7 @@ module API
post ":id/merge_requests" do post ":id/merge_requests" do
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42126') Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-ce/issues/42126')
authorize! :create_merge_request, user_project authorize! :create_merge_request_from, user_project
mr_params = declared_params(include_missing: false) mr_params = declared_params(include_missing: false)
mr_params[:force_remove_source_branch] = mr_params.delete(:remove_source_branch) if mr_params[:remove_source_branch].present? mr_params[:force_remove_source_branch] = mr_params.delete(:remove_source_branch) if mr_params[:remove_source_branch].present?
......
...@@ -23,7 +23,8 @@ module Gitlab ...@@ -23,7 +23,8 @@ module Gitlab
def execute def execute
raise ProjectNotFound unless project raise ProjectNotFound unless project
validate_permission!(:create_merge_request) validate_permission!(:create_merge_request_in)
validate_permission!(:create_merge_request_from)
verify_record!( verify_record!(
record: create_merge_request, record: create_merge_request,
......
...@@ -51,7 +51,7 @@ module Gitlab ...@@ -51,7 +51,7 @@ module Gitlab
return false unless can_access_git? return false unless can_access_git?
if protected?(ProtectedBranch, project, ref) if protected?(ProtectedBranch, project, ref)
user.can?(:delete_protected_branch, project) user.can?(:push_to_delete_protected_branch, project)
else else
user.can?(:push_code, project) user.can?(:push_code, project)
end end
......
require 'spec_helper'
describe ChecksCollaboration do
include ProjectForksHelper
let(:helper) do
fake_class = Class.new(ApplicationController) do
include ChecksCollaboration
end
fake_class.new
end
describe '#can_collaborate_with_project?' do
let(:user) { create(:user) }
let(:project) { create(:project, :public) }
before do
allow(helper).to receive(:current_user).and_return(user)
allow(helper).to receive(:can?) do |user, ability, subject|
Ability.allowed?(user, ability, subject)
end
end
it 'is true if the user can push to the project' do
project.add_developer(user)
expect(helper.can_collaborate_with_project?(project)).to be_truthy
end
it 'is true when the user can push to a branch of the project' do
fake_access = double('Gitlab::UserAccess')
expect(fake_access).to receive(:can_push_to_branch?).with('a-branch').and_return(true)
expect(Gitlab::UserAccess).to receive(:new).with(user, project: project).and_return(fake_access)
expect(helper.can_collaborate_with_project?(project, ref: 'a-branch')).to be_truthy
end
context 'when the user has forked the project' do
before do
fork_project(project, user, namespace: user.namespace)
end
it 'is true' do
expect(helper.can_collaborate_with_project?(project)).to be_truthy
end
it 'is false when the project is archived' do
project.archived = true
expect(helper.can_collaborate_with_project?(project)).to be_falsy
end
end
end
end
...@@ -938,7 +938,7 @@ describe Projects::IssuesController do ...@@ -938,7 +938,7 @@ describe Projects::IssuesController do
end end
describe 'POST create_merge_request' do describe 'POST create_merge_request' do
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository, :public) }
before do before do
project.add_developer(user) project.add_developer(user)
...@@ -955,6 +955,22 @@ describe Projects::IssuesController do ...@@ -955,6 +955,22 @@ describe Projects::IssuesController do
expect(response).to match_response_schema('merge_request') expect(response).to match_response_schema('merge_request')
end end
it 'is not available when the project is archived' do
project.update!(archived: true)
create_merge_request
expect(response).to have_gitlab_http_status(404)
end
it 'is not available for users who cannot create merge requests' do
sign_in(create(:user))
create_merge_request
expect(response).to have_gitlab_http_status(404)
end
def create_merge_request def create_merge_request
post :create_merge_request, namespace_id: project.namespace.to_param, post :create_merge_request, namespace_id: project.namespace.to_param,
project_id: project.to_param, project_id: project.to_param,
......
...@@ -4,6 +4,10 @@ FactoryBot.define do ...@@ -4,6 +4,10 @@ FactoryBot.define do
user user
awardable factory: :issue awardable factory: :issue
after(:create) do |award, evaluator|
award.awardable.project.add_guest(evaluator.user)
end
trait :upvote trait :upvote
trait :downvote do trait :downvote do
name "thumbsdown" name "thumbsdown"
......
...@@ -45,7 +45,7 @@ feature 'Admin Broadcast Messages' do ...@@ -45,7 +45,7 @@ feature 'Admin Broadcast Messages' do
page.within('.broadcast-message-preview') do page.within('.broadcast-message-preview') do
expect(page).to have_selector('strong', text: 'Markdown') expect(page).to have_selector('strong', text: 'Markdown')
expect(page).to have_selector('gl-emoji[data-name="tada"]') expect(page).to have_emoji('tada')
end end
end end
end end
...@@ -98,7 +98,7 @@ feature 'Group show page' do ...@@ -98,7 +98,7 @@ feature 'Group show page' do
it 'shows the project info' do it 'shows the project info' do
expect(page).to have_content(project.title) expect(page).to have_content(project.title)
expect(page).to have_selector('gl-emoji[data-name="smile"]') expect(page).to have_emoji('smile')
end end
end end
end end
...@@ -35,6 +35,14 @@ describe 'Merge request > User awards emoji', :js do ...@@ -35,6 +35,14 @@ describe 'Merge request > User awards emoji', :js do
expect(page).to have_selector('.emoji-menu', count: 1) expect(page).to have_selector('.emoji-menu', count: 1)
end end
describe 'the project is archived' do
let(:project) { create(:project, :public, :repository, :archived) }
it 'does not see award menu button' do
expect(page).not_to have_selector('.js-award-holder')
end
end
end end
describe 'logged out' do describe 'logged out' do
......
...@@ -40,6 +40,14 @@ describe 'Merge request > User cherry-picks', :js do ...@@ -40,6 +40,14 @@ describe 'Merge request > User cherry-picks', :js do
expect(page).to have_link 'Cherry-pick' expect(page).to have_link 'Cherry-pick'
end end
it 'hides the cherry pick button for an archived project' do
project.update!(archived: true)
visit project_merge_request_path(project, merge_request)
expect(page).not_to have_link 'Cherry-pick'
end
end end
end end
end end
...@@ -99,6 +99,74 @@ describe 'User interacts with awards in an issue', :js do ...@@ -99,6 +99,74 @@ describe 'User interacts with awards in an issue', :js do
click_button('Comment') click_button('Comment')
end end
expect(page).to have_selector('gl-emoji[data-name="smile"]') expect(page).to have_emoji('smile')
end
context 'when a project is archived' do
let(:project) { create(:project, :archived) }
it 'hides the add award button' do
page.within('.awards') do
expect(page).not_to have_css('.js-add-award')
end
end
end
context 'awards on a note' do
let!(:note) { create(:note, noteable: issue, project: issue.project) }
let!(:award_emoji) { create(:award_emoji, awardable: note, name: '100') }
it 'shows the award on the note' do
page.within('.note-awards') do
expect(page).to have_emoji('100')
end
end
it 'allows adding a vote to an award' do
page.within('.note-awards') do
find('gl-emoji[data-name="100"]').click
end
wait_for_requests
expect(note.reload.award_emoji.size).to eq(2)
end
it 'allows adding a new emoji' do
page.within('.note-actions') do
find('a.js-add-award').click
end
page.within('.emoji-menu-content') do
find('gl-emoji[data-name="8ball"]').click
end
wait_for_requests
page.within('.note-awards') do
expect(page).to have_emoji('8ball')
end
expect(note.reload.award_emoji.size).to eq(2)
end
context 'when the project is archived' do
let(:project) { create(:project, :archived) }
it 'hides the buttons for adding new emoji' do
page.within('.note-awards') do
expect(page).not_to have_css('.award-menu-holder')
end
page.within('.note-actions') do
expect(page).not_to have_css('a.js-add-award')
end
end
it 'does not allow toggling existing emoji' do
page.within('.note-awards') do
find('gl-emoji[data-name="100"]').click
end
wait_for_requests
expect(note.reload.award_emoji.size).to eq(1)
end
end
end end
end end
...@@ -195,6 +195,26 @@ describe 'Branches' do ...@@ -195,6 +195,26 @@ describe 'Branches' do
expect(page).to have_content("Protected branches can be managed in project settings") expect(page).to have_content("Protected branches can be managed in project settings")
end end
end end
it 'shows the merge request button' do
visit project_branches_path(project)
page.within first('.all-branches li') do
expect(page).to have_content 'Merge request'
end
end
context 'when the project is archived' do
let(:project) { create(:project, :public, :repository, :archived) }
it 'does not show the merge request button when the project is archived' do
visit project_branches_path(project)
page.within first('.all-branches li') do
expect(page).not_to have_content 'Merge request'
end
end
end
end end
context 'logged out' do context 'logged out' do
...@@ -204,7 +224,7 @@ describe 'Branches' do ...@@ -204,7 +224,7 @@ describe 'Branches' do
it 'does not show merge request button' do it 'does not show merge request button' do
page.within first('.all-branches li') do page.within first('.all-branches li') do
expect(page).not_to have_content 'Merge Request' expect(page).not_to have_content 'Merge request'
end end
end end
end end
......
...@@ -89,4 +89,15 @@ describe 'Cherry-pick Commits' do ...@@ -89,4 +89,15 @@ describe 'Cherry-pick Commits' do
expect(page).to have_content('The commit has been successfully cherry-picked.') expect(page).to have_content('The commit has been successfully cherry-picked.')
end end
end end
context 'when the project is archived' do
let(:project) { create(:project, :repository, :archived, namespace: group) }
it 'does not show the cherry-pick link' do
find('.header-action-buttons a.dropdown-toggle').click
expect(page).not_to have_text("Cherry-pick")
expect(page).not_to have_css("a[href='#modal-cherry-pick-commit']")
end
end
end end
...@@ -10,13 +10,16 @@ describe 'User reverts a commit', :js do ...@@ -10,13 +10,16 @@ describe 'User reverts a commit', :js do
sign_in(user) sign_in(user)
visit(project_commit_path(project, sample_commit.id)) visit(project_commit_path(project, sample_commit.id))
end
def click_revert
find('.header-action-buttons .dropdown').click find('.header-action-buttons .dropdown').click
find('a[href="#modal-revert-commit"]').click find('a[href="#modal-revert-commit"]').click
end end
context 'without creating a new merge request' do context 'without creating a new merge request' do
before do before do
click_revert
page.within('#modal-revert-commit') do page.within('#modal-revert-commit') do
uncheck('create_merge_request') uncheck('create_merge_request')
click_button('Revert') click_button('Revert')
...@@ -44,6 +47,10 @@ describe 'User reverts a commit', :js do ...@@ -44,6 +47,10 @@ describe 'User reverts a commit', :js do
end end
context 'with creating a new merge request' do context 'with creating a new merge request' do
before do
click_revert
end
it 'reverts a commit' do it 'reverts a commit' do
page.within('#modal-revert-commit') do page.within('#modal-revert-commit') do
click_button('Revert') click_button('Revert')
...@@ -53,4 +60,14 @@ describe 'User reverts a commit', :js do ...@@ -53,4 +60,14 @@ describe 'User reverts a commit', :js do
expect(page).to have_content("From revert-#{Commit.truncate_sha(sample_commit.id)} into master") expect(page).to have_content("From revert-#{Commit.truncate_sha(sample_commit.id)} into master")
end end
end end
context 'when the project is archived' do
let(:project) { create(:project, :repository, :archived, namespace: user.namespace) }
it 'does not show the revert link' do
find('.header-action-buttons .dropdown').click
expect(page).not_to have_link('Revert')
end
end
end end
...@@ -12,6 +12,23 @@ describe 'Projects > Files > User edits files' do ...@@ -12,6 +12,23 @@ describe 'Projects > Files > User edits files' do
sign_in(user) sign_in(user)
end end
shared_examples 'unavailable for an archived project' do
it 'does not show the edit link for an archived project', :js do
project.update!(archived: true)
visit project_tree_path(project, project.repository.root_ref)
click_link('.gitignore')
aggregate_failures 'available edit buttons' do
expect(page).not_to have_text('Edit')
expect(page).not_to have_text('Web IDE')
expect(page).not_to have_text('Replace')
expect(page).not_to have_text('Delete')
end
end
end
context 'when an user has write access' do context 'when an user has write access' do
before do before do
project.add_master(user) project.add_master(user)
...@@ -85,6 +102,8 @@ describe 'Projects > Files > User edits files' do ...@@ -85,6 +102,8 @@ describe 'Projects > Files > User edits files' do
expect(page).to have_css('.line_holder.new') expect(page).to have_css('.line_holder.new')
end end
it_behaves_like 'unavailable for an archived project'
end end
context 'when an user does not have write access' do context 'when an user does not have write access' do
...@@ -168,6 +187,10 @@ describe 'Projects > Files > User edits files' do ...@@ -168,6 +187,10 @@ describe 'Projects > Files > User edits files' do
expect(page).to have_content("From #{forked_project.full_path}") expect(page).to have_content("From #{forked_project.full_path}")
expect(page).to have_content("into #{project2.full_path}") expect(page).to have_content("into #{project2.full_path}")
end end
it_behaves_like 'unavailable for an archived project' do
let(:project) { project2 }
end
end end
end end
end end
...@@ -6,11 +6,27 @@ describe "User views issue" do ...@@ -6,11 +6,27 @@ describe "User views issue" do
set(:issue) { create(:issue, project: project, description: "# Description header", author: user) } set(:issue) { create(:issue, project: project, description: "# Description header", author: user) }
before do before do
project.add_guest(user) project.add_developer(user)
sign_in(user) sign_in(user)
visit(project_issue_path(project, issue)) visit(project_issue_path(project, issue))
end end
it { expect(page).to have_header_with_correct_id_and_link(1, "Description header", "description-header") } it { expect(page).to have_header_with_correct_id_and_link(1, "Description header", "description-header") }
it 'shows the merge request and issue actions', :aggregate_failures do
expect(page).to have_link('New issue')
expect(page).to have_button('Create merge request')
expect(page).to have_link('Close issue')
end
context 'when the project is archived' do
let(:project) { create(:project, :public, :archived) }
it 'hides the merge request and issue actions', :aggregate_failures do
expect(page).not_to have_link('New issue')
expect(page).not_to have_button('Create merge request')
expect(page).not_to have_link('Close issue')
end
end
end end
...@@ -45,6 +45,18 @@ feature 'Merge Request button' do ...@@ -45,6 +45,18 @@ feature 'Merge Request button' do
end end
end end
end end
context 'when the project is archived' do
it 'hides the link' do
project.update!(archived: true)
visit url
within("#content-body") do
expect(page).not_to have_link(label)
end
end
end
end end
context 'logged in as non-member' do context 'logged in as non-member' do
......
...@@ -56,4 +56,12 @@ describe 'User reverts a merge request', :js do ...@@ -56,4 +56,12 @@ describe 'User reverts a merge request', :js do
expect(page).to have_content('The merge request has been successfully reverted. You can now submit a merge request to get this change into the original branch.') expect(page).to have_content('The merge request has been successfully reverted. You can now submit a merge request to get this change into the original branch.')
end end
it 'cannot revert a merge requests for an archived project' do
project.update!(archived: true)
visit(merge_request_path(merge_request))
expect(page).not_to have_link('Revert')
end
end end
...@@ -94,6 +94,18 @@ describe 'User views open merge requests' do ...@@ -94,6 +94,18 @@ describe 'User views open merge requests' do
end end
include_examples 'shows merge requests' include_examples 'shows merge requests'
it 'shows the new merge request button' do
expect(page).to have_link('New merge request')
end
context 'when the project is archived' do
let(:project) { create(:project, :public, :repository, :archived) }
it 'hides the new merge request button' do
expect(page).not_to have_link('New merge request')
end
end
end end
end end
......
require 'spec_helper'
describe 'Projects > Show > Collaboration links' do
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
before do
project.add_developer(user)
sign_in(user)
end
it 'shows all the expected links' do
visit project_path(project)
# The navigation bar
page.within('.header-new') do
aggregate_failures 'dropdown links in the navigation bar' do
expect(page).to have_link('New issue')
expect(page).to have_link('New merge request')
expect(page).to have_link('New snippet', href: new_project_snippet_path(project))
end
end
# The project header
page.within('.project-home-panel') do
aggregate_failures 'dropdown links in the project home panel' do
expect(page).to have_link('New issue')
expect(page).to have_link('New merge request')
expect(page).to have_link('New snippet')
expect(page).to have_link('New file')
expect(page).to have_link('New branch')
expect(page).to have_link('New tag')
end
end
# The dropdown above the tree
page.within('.repo-breadcrumb') do
aggregate_failures 'dropdown links above the repo tree' do
expect(page).to have_link('New file')
expect(page).to have_link('Upload file')
expect(page).to have_link('New directory')
expect(page).to have_link('New branch')
expect(page).to have_link('New tag')
end
end
# The Web IDE
expect(page).to have_link('Web IDE')
end
it 'hides the links when the project is archived' do
project.update!(archived: true)
visit project_path(project)
page.within('.header-new') do
aggregate_failures 'dropdown links' do
expect(page).not_to have_link('New issue')
expect(page).not_to have_link('New merge request')
expect(page).not_to have_link('New snippet', href: new_project_snippet_path(project))
end
end
page.within('.project-home-panel') do
aggregate_failures 'dropdown links' do
expect(page).not_to have_link('New issue')
expect(page).not_to have_link('New merge request')
expect(page).not_to have_link('New snippet')
expect(page).not_to have_link('New file')
expect(page).not_to have_link('New branch')
expect(page).not_to have_link('New tag')
end
end
page.within('.repo-breadcrumb') do
aggregate_failures 'dropdown links' do
expect(page).not_to have_link('New file')
expect(page).not_to have_link('Upload file')
expect(page).not_to have_link('New directory')
expect(page).not_to have_link('New branch')
expect(page).not_to have_link('New tag')
end
end
expect(page).not_to have_link('Web IDE')
end
end
...@@ -19,6 +19,12 @@ describe MergeRequestTargetProjectFinder do ...@@ -19,6 +19,12 @@ describe MergeRequestTargetProjectFinder do
expect(finder.execute).to contain_exactly(forked_project) expect(finder.execute).to contain_exactly(forked_project)
end end
it 'does not contain archived projects' do
base_project.update!(archived: true)
expect(finder.execute).to contain_exactly(other_fork, forked_project)
end
end end
context 'public projects' do context 'public projects' do
......
...@@ -96,13 +96,32 @@ describe IssuesHelper do ...@@ -96,13 +96,32 @@ describe IssuesHelper do
describe '#award_state_class' do describe '#award_state_class' do
let!(:upvote) { create(:award_emoji) } let!(:upvote) { create(:award_emoji) }
let(:awardable) { upvote.awardable }
let(:user) { upvote.user }
before do
allow(helper).to receive(:can?) do |*args|
Ability.allowed?(*args)
end
end
it "returns disabled string for unauthenticated user" do it "returns disabled string for unauthenticated user" do
expect(award_state_class(AwardEmoji.all, nil)).to eq("disabled") expect(helper.award_state_class(awardable, AwardEmoji.all, nil)).to eq("disabled")
end
it "returns disabled for a user that does not have access to the awardable" do
expect(helper.award_state_class(awardable, AwardEmoji.all, build(:user))).to eq("disabled")
end end
it "returns active string for author" do it "returns active string for author" do
expect(award_state_class(AwardEmoji.all, upvote.user)).to eq("active") expect(helper.award_state_class(awardable, AwardEmoji.all, upvote.user)).to eq("active")
end
it "is blank for a user that has access to the awardable" do
user = build(:user)
expect(helper).to receive(:can?).with(user, :award_emoji, awardable).and_return(true)
expect(helper.award_state_class(awardable, AwardEmoji.all, user)).to be_blank
end end
end end
...@@ -144,4 +163,26 @@ describe IssuesHelper do ...@@ -144,4 +163,26 @@ describe IssuesHelper do
end end
end end
end end
describe '#show_new_issue_link?' do
before do
allow(helper).to receive(:current_user)
end
it 'is false when no project there is no project' do
expect(helper.show_new_issue_link?(nil)).to be_falsey
end
it 'is true when there is a project and no logged in user' do
expect(helper.show_new_issue_link?(build(:project))).to be_truthy
end
it 'is true when the current user does not have access to the project' do
project = build(:project)
allow(helper).to receive(:current_user).and_return(project.owner)
expect(helper).to receive(:can?).with(project.owner, :create_issue, project).and_return(true)
expect(helper.show_new_issue_link?(project)).to be_truthy
end
end
end end
...@@ -3,7 +3,7 @@ import store from '~/notes/stores'; ...@@ -3,7 +3,7 @@ import store from '~/notes/stores';
import noteActions from '~/notes/components/note_actions.vue'; import noteActions from '~/notes/components/note_actions.vue';
import { userDataMock } from '../mock_data'; import { userDataMock } from '../mock_data';
describe('issse_note_actions component', () => { describe('issue_note_actions component', () => {
let vm; let vm;
let Component; let Component;
...@@ -24,6 +24,7 @@ describe('issse_note_actions component', () => { ...@@ -24,6 +24,7 @@ describe('issse_note_actions component', () => {
authorId: 26, authorId: 26,
canDelete: true, canDelete: true,
canEdit: true, canEdit: true,
canAwardEmoji: true,
canReportAsAbuse: true, canReportAsAbuse: true,
noteId: 539, noteId: 539,
reportAbusePath: '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F7%23note_539&user_id=26', reportAbusePath: '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F7%23note_539&user_id=26',
...@@ -70,6 +71,7 @@ describe('issse_note_actions component', () => { ...@@ -70,6 +71,7 @@ describe('issse_note_actions component', () => {
authorId: 26, authorId: 26,
canDelete: false, canDelete: false,
canEdit: false, canEdit: false,
canAwardEmoji: false,
canReportAsAbuse: false, canReportAsAbuse: false,
noteId: 539, noteId: 539,
reportAbusePath: '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F7%23note_539&user_id=26', reportAbusePath: '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F7%23note_539&user_id=26',
......
...@@ -29,6 +29,7 @@ describe('note_awards_list component', () => { ...@@ -29,6 +29,7 @@ describe('note_awards_list component', () => {
awards: awardsMock, awards: awardsMock,
noteAuthorId: 2, noteAuthorId: 2,
noteId: 545, noteId: 545,
canAwardEmoji: true,
toggleAwardPath: '/gitlab-org/gitlab-ce/notes/545/toggle_award_emoji', toggleAwardPath: '/gitlab-org/gitlab-ce/notes/545/toggle_award_emoji',
}, },
}).$mount(); }).$mount();
...@@ -43,14 +44,45 @@ describe('note_awards_list component', () => { ...@@ -43,14 +44,45 @@ describe('note_awards_list component', () => {
expect(vm.$el.querySelector('.js-awards-block button [data-name="cartwheel_tone3"]')).toBeDefined(); expect(vm.$el.querySelector('.js-awards-block button [data-name="cartwheel_tone3"]')).toBeDefined();
}); });
it('should be possible to remove awareded emoji', () => { it('should be possible to remove awarded emoji', () => {
spyOn(vm, 'handleAward').and.callThrough(); spyOn(vm, 'handleAward').and.callThrough();
spyOn(vm, 'toggleAwardRequest').and.callThrough();
vm.$el.querySelector('.js-awards-block button').click(); vm.$el.querySelector('.js-awards-block button').click();
expect(vm.handleAward).toHaveBeenCalledWith('flag_tz'); expect(vm.handleAward).toHaveBeenCalledWith('flag_tz');
expect(vm.toggleAwardRequest).toHaveBeenCalled();
}); });
it('should be possible to add new emoji', () => { it('should be possible to add new emoji', () => {
expect(vm.$el.querySelector('.js-add-award')).toBeDefined(); expect(vm.$el.querySelector('.js-add-award')).toBeDefined();
}); });
describe('when the user cannot award emoji', () => {
beforeEach(() => {
const Component = Vue.extend(awardsNote);
vm = new Component({
store,
propsData: {
awards: awardsMock,
noteAuthorId: 2,
noteId: 545,
canAwardEmoji: false,
toggleAwardPath: '/gitlab-org/gitlab-ce/notes/545/toggle_award_emoji',
},
}).$mount();
});
it('should not be possible to remove awarded emoji', () => {
spyOn(vm, 'toggleAwardRequest').and.callThrough();
vm.$el.querySelector('.js-awards-block button').click();
expect(vm.toggleAwardRequest).not.toHaveBeenCalled();
});
it('should not be possible to add new emoji', () => {
expect(vm.$el.querySelector('.js-add-award')).toBeNull();
});
});
}); });
...@@ -18,6 +18,7 @@ describe('issue_note_body component', () => { ...@@ -18,6 +18,7 @@ describe('issue_note_body component', () => {
propsData: { propsData: {
note, note,
canEdit: true, canEdit: true,
canAwardEmoji: true,
}, },
}).$mount(); }).$mount();
}); });
......
...@@ -9,6 +9,7 @@ export const notesDataMock = { ...@@ -9,6 +9,7 @@ export const notesDataMock = {
totalNotes: 1, totalNotes: 1,
closePath: '/twitter/flight/issues/9.json?issue%5Bstate_event%5D=close', closePath: '/twitter/flight/issues/9.json?issue%5Bstate_event%5D=close',
reopenPath: '/twitter/flight/issues/9.json?issue%5Bstate_event%5D=reopen', reopenPath: '/twitter/flight/issues/9.json?issue%5Bstate_event%5D=reopen',
canAwardEmoji: true,
}; };
export const userDataMock = { export const userDataMock = {
...@@ -30,6 +31,7 @@ export const noteableDataMock = { ...@@ -30,6 +31,7 @@ export const noteableDataMock = {
current_user: { current_user: {
can_create_note: true, can_create_note: true,
can_update: true, can_update: true,
can_award_emoji: true,
}, },
description: '', description: '',
due_date: null, due_date: null,
...@@ -86,7 +88,10 @@ export const individualNote = { ...@@ -86,7 +88,10 @@ export const individualNote = {
human_access: 'Owner', human_access: 'Owner',
note: 'sdfdsaf', note: 'sdfdsaf',
note_html: "<p dir='auto'>sdfdsaf</p>", note_html: "<p dir='auto'>sdfdsaf</p>",
current_user: { can_edit: true }, current_user: {
can_edit: true,
can_award_emoji: true,
},
discussion_id: '0fb4e0e3f9276e55ff32eb4195add694aece4edd', discussion_id: '0fb4e0e3f9276e55ff32eb4195add694aece4edd',
emoji_awardable: true, emoji_awardable: true,
award_emoji: [ award_emoji: [
...@@ -129,6 +134,7 @@ export const note = { ...@@ -129,6 +134,7 @@ export const note = {
note_html: '<p dir="auto">Vel id placeat reprehenderit sit numquam.</p>', note_html: '<p dir="auto">Vel id placeat reprehenderit sit numquam.</p>',
current_user: { current_user: {
can_edit: true, can_edit: true,
can_award_emoji: true,
}, },
discussion_id: 'd3842a451b7f3d9a5dfce329515127b2d29a4cd0', discussion_id: 'd3842a451b7f3d9a5dfce329515127b2d29a4cd0',
emoji_awardable: true, emoji_awardable: true,
...@@ -187,6 +193,7 @@ export const discussionMock = { ...@@ -187,6 +193,7 @@ export const discussionMock = {
note_html: "<p dir='auto'>THIS IS A DICUSSSION!</p>", note_html: "<p dir='auto'>THIS IS A DICUSSSION!</p>",
current_user: { current_user: {
can_edit: true, can_edit: true,
can_award_emoji: true,
}, },
discussion_id: '9e3bd2f71a01de45fd166e6719eb380ad9f270b1', discussion_id: '9e3bd2f71a01de45fd166e6719eb380ad9f270b1',
emoji_awardable: true, emoji_awardable: true,
...@@ -231,6 +238,7 @@ export const discussionMock = { ...@@ -231,6 +238,7 @@ export const discussionMock = {
}, },
current_user: { current_user: {
can_edit: true, can_edit: true,
can_award_emoji: true,
}, },
discussion_id: '9e3bd2f71a01de45fd166e6719eb380ad9f270b1', discussion_id: '9e3bd2f71a01de45fd166e6719eb380ad9f270b1',
emoji_awardable: true, emoji_awardable: true,
...@@ -275,6 +283,7 @@ export const discussionMock = { ...@@ -275,6 +283,7 @@ export const discussionMock = {
}, },
current_user: { current_user: {
can_edit: true, can_edit: true,
can_award_emoji: true,
}, },
discussion_id: '9e3bd2f71a01de45fd166e6719eb380ad9f270b1', discussion_id: '9e3bd2f71a01de45fd166e6719eb380ad9f270b1',
emoji_awardable: true, emoji_awardable: true,
...@@ -365,6 +374,7 @@ export const INDIVIDUAL_NOTE_RESPONSE_MAP = { ...@@ -365,6 +374,7 @@ export const INDIVIDUAL_NOTE_RESPONSE_MAP = {
note_html: '\u003cp dir="auto"\u003esdfdsaf\u003c/p\u003e', note_html: '\u003cp dir="auto"\u003esdfdsaf\u003c/p\u003e',
current_user: { current_user: {
can_edit: true, can_edit: true,
can_award_emoji: true,
}, },
discussion_id: '0fb4e0e3f9276e55ff32eb4195add694aece4edd', discussion_id: '0fb4e0e3f9276e55ff32eb4195add694aece4edd',
emoji_awardable: true, emoji_awardable: true,
...@@ -425,6 +435,7 @@ export const INDIVIDUAL_NOTE_RESPONSE_MAP = { ...@@ -425,6 +435,7 @@ export const INDIVIDUAL_NOTE_RESPONSE_MAP = {
note_html: '\u003cp dir="auto"\u003eNew note!\u003c/p\u003e', note_html: '\u003cp dir="auto"\u003eNew note!\u003c/p\u003e',
current_user: { current_user: {
can_edit: true, can_edit: true,
can_award_emoji: true,
}, },
discussion_id: '70d5c92a4039a36c70100c6691c18c27e4b0a790', discussion_id: '70d5c92a4039a36c70100c6691c18c27e4b0a790',
emoji_awardable: true, emoji_awardable: true,
...@@ -478,6 +489,7 @@ export const INDIVIDUAL_NOTE_RESPONSE_MAP = { ...@@ -478,6 +489,7 @@ export const INDIVIDUAL_NOTE_RESPONSE_MAP = {
}, },
current_user: { current_user: {
can_edit: true, can_edit: true,
can_award_emoji: true,
}, },
discussion_id: 'a3ed36e29b1957efb3b68c53e2d7a2b24b1df052', discussion_id: 'a3ed36e29b1957efb3b68c53e2d7a2b24b1df052',
emoji_awardable: true, emoji_awardable: true,
...@@ -527,6 +539,7 @@ export const DISCUSSION_NOTE_RESPONSE_MAP = { ...@@ -527,6 +539,7 @@ export const DISCUSSION_NOTE_RESPONSE_MAP = {
note_html: '\u003cp dir="auto"\u003eAdding a comment\u003c/p\u003e', note_html: '\u003cp dir="auto"\u003eAdding a comment\u003c/p\u003e',
current_user: { current_user: {
can_edit: true, can_edit: true,
can_award_emoji: true,
}, },
discussion_id: 'a3ed36e29b1957efb3b68c53e2d7a2b24b1df052', discussion_id: 'a3ed36e29b1957efb3b68c53e2d7a2b24b1df052',
emoji_awardable: true, emoji_awardable: true,
......
...@@ -7,62 +7,6 @@ describe Ability do ...@@ -7,62 +7,6 @@ describe Ability do
end end
end end
describe '.can_edit_note?' do
let(:project) { create(:project) }
let(:note) { create(:note_on_issue, project: project) }
context 'using an anonymous user' do
it 'returns false' do
expect(described_class.can_edit_note?(nil, note)).to be_falsy
end
end
context 'using a system note' do
it 'returns false' do
system_note = create(:note, system: true)
user = create(:user)
expect(described_class.can_edit_note?(user, system_note)).to be_falsy
end
end
context 'using users with different access levels' do
let(:user) { create(:user) }
it 'returns true for the author' do
expect(described_class.can_edit_note?(note.author, note)).to be_truthy
end
it 'returns false for a guest user' do
project.add_guest(user)
expect(described_class.can_edit_note?(user, note)).to be_falsy
end
it 'returns false for a developer' do
project.add_developer(user)
expect(described_class.can_edit_note?(user, note)).to be_falsy
end
it 'returns true for a master' do
project.add_master(user)
expect(described_class.can_edit_note?(user, note)).to be_truthy
end
it 'returns true for a group owner' do
group = create(:group)
project.project_group_links.create(
group: group,
group_access: Gitlab::Access::MASTER)
group.add_owner(user)
expect(described_class.can_edit_note?(user, note)).to be_truthy
end
end
end
describe '.users_that_can_read_project' do describe '.users_that_can_read_project' do
context 'using a public project' do context 'using a public project' do
it 'returns all the users' do it 'returns all the users' do
......
...@@ -46,6 +46,31 @@ describe Awardable do ...@@ -46,6 +46,31 @@ describe Awardable do
end end
end end
describe '#user_can_award?' do
let(:user) { create(:user) }
before do
issue.project.add_guest(user)
end
it 'does not allow upvoting or downvoting your own issue' do
issue.update!(author: user)
expect(issue.user_can_award?(user, AwardEmoji::DOWNVOTE_NAME)).to be_falsy
expect(issue.user_can_award?(user, AwardEmoji::UPVOTE_NAME)).to be_falsy
end
it 'is truthy when the user is allowed to award emoji' do
expect(issue.user_can_award?(user, AwardEmoji::UPVOTE_NAME)).to be_truthy
end
it 'is falsy when the project is archived' do
issue.project.update!(archived: true)
expect(issue.user_can_award?(user, AwardEmoji::UPVOTE_NAME)).to be_falsy
end
end
describe "#toggle_award_emoji" do describe "#toggle_award_emoji" do
it "adds an emoji if it isn't awarded yet" do it "adds an emoji if it isn't awarded yet" do
expect { issue.toggle_award_emoji("thumbsup", award_emoji.user) }.to change { AwardEmoji.count }.by(1) expect { issue.toggle_award_emoji("thumbsup", award_emoji.user) }.to change { AwardEmoji.count }.by(1)
......
...@@ -18,7 +18,6 @@ describe NotePolicy, mdoels: true do ...@@ -18,7 +18,6 @@ describe NotePolicy, mdoels: true do
context 'when the project is public' do context 'when the project is public' do
context 'when the note author is not a project member' do context 'when the note author is not a project member' do
it 'can edit a note' do it 'can edit a note' do
expect(policies).to be_allowed(:update_note)
expect(policies).to be_allowed(:admin_note) expect(policies).to be_allowed(:admin_note)
expect(policies).to be_allowed(:resolve_note) expect(policies).to be_allowed(:resolve_note)
expect(policies).to be_allowed(:read_note) expect(policies).to be_allowed(:read_note)
...@@ -29,7 +28,6 @@ describe NotePolicy, mdoels: true do ...@@ -29,7 +28,6 @@ describe NotePolicy, mdoels: true do
it 'can edit note' do it 'can edit note' do
policies = policies(create(:project_snippet, project: project)) policies = policies(create(:project_snippet, project: project))
expect(policies).to be_allowed(:update_note)
expect(policies).to be_allowed(:admin_note) expect(policies).to be_allowed(:admin_note)
expect(policies).to be_allowed(:resolve_note) expect(policies).to be_allowed(:resolve_note)
expect(policies).to be_allowed(:read_note) expect(policies).to be_allowed(:read_note)
...@@ -47,7 +45,6 @@ describe NotePolicy, mdoels: true do ...@@ -47,7 +45,6 @@ describe NotePolicy, mdoels: true do
end end
it 'can edit a note' do it 'can edit a note' do
expect(policies).to be_allowed(:update_note)
expect(policies).to be_allowed(:admin_note) expect(policies).to be_allowed(:admin_note)
expect(policies).to be_allowed(:resolve_note) expect(policies).to be_allowed(:resolve_note)
expect(policies).to be_allowed(:read_note) expect(policies).to be_allowed(:read_note)
...@@ -56,7 +53,6 @@ describe NotePolicy, mdoels: true do ...@@ -56,7 +53,6 @@ describe NotePolicy, mdoels: true do
context 'when the note author is not a project member' do context 'when the note author is not a project member' do
it 'can not edit a note' do it 'can not edit a note' do
expect(policies).to be_disallowed(:update_note)
expect(policies).to be_disallowed(:admin_note) expect(policies).to be_disallowed(:admin_note)
expect(policies).to be_disallowed(:resolve_note) expect(policies).to be_disallowed(:resolve_note)
end end
......
...@@ -27,6 +27,7 @@ describe PersonalSnippetPolicy do ...@@ -27,6 +27,7 @@ describe PersonalSnippetPolicy do
it do it do
is_expected.to be_allowed(:read_personal_snippet) is_expected.to be_allowed(:read_personal_snippet)
is_expected.to be_disallowed(:comment_personal_snippet) is_expected.to be_disallowed(:comment_personal_snippet)
is_expected.to be_disallowed(:award_emoji)
is_expected.to be_disallowed(*author_permissions) is_expected.to be_disallowed(*author_permissions)
end end
end end
...@@ -37,6 +38,7 @@ describe PersonalSnippetPolicy do ...@@ -37,6 +38,7 @@ describe PersonalSnippetPolicy do
it do it do
is_expected.to be_allowed(:read_personal_snippet) is_expected.to be_allowed(:read_personal_snippet)
is_expected.to be_allowed(:comment_personal_snippet) is_expected.to be_allowed(:comment_personal_snippet)
is_expected.to be_allowed(:award_emoji)
is_expected.to be_disallowed(*author_permissions) is_expected.to be_disallowed(*author_permissions)
end end
end end
...@@ -47,6 +49,7 @@ describe PersonalSnippetPolicy do ...@@ -47,6 +49,7 @@ describe PersonalSnippetPolicy do
it do it do
is_expected.to be_allowed(:read_personal_snippet) is_expected.to be_allowed(:read_personal_snippet)
is_expected.to be_allowed(:comment_personal_snippet) is_expected.to be_allowed(:comment_personal_snippet)
is_expected.to be_allowed(:award_emoji)
is_expected.to be_allowed(*author_permissions) is_expected.to be_allowed(*author_permissions)
end end
end end
...@@ -61,6 +64,7 @@ describe PersonalSnippetPolicy do ...@@ -61,6 +64,7 @@ describe PersonalSnippetPolicy do
it do it do
is_expected.to be_disallowed(:read_personal_snippet) is_expected.to be_disallowed(:read_personal_snippet)
is_expected.to be_disallowed(:comment_personal_snippet) is_expected.to be_disallowed(:comment_personal_snippet)
is_expected.to be_disallowed(:award_emoji)
is_expected.to be_disallowed(*author_permissions) is_expected.to be_disallowed(*author_permissions)
end end
end end
...@@ -71,6 +75,7 @@ describe PersonalSnippetPolicy do ...@@ -71,6 +75,7 @@ describe PersonalSnippetPolicy do
it do it do
is_expected.to be_allowed(:read_personal_snippet) is_expected.to be_allowed(:read_personal_snippet)
is_expected.to be_allowed(:comment_personal_snippet) is_expected.to be_allowed(:comment_personal_snippet)
is_expected.to be_allowed(:award_emoji)
is_expected.to be_disallowed(*author_permissions) is_expected.to be_disallowed(*author_permissions)
end end
end end
...@@ -81,6 +86,7 @@ describe PersonalSnippetPolicy do ...@@ -81,6 +86,7 @@ describe PersonalSnippetPolicy do
it do it do
is_expected.to be_disallowed(:read_personal_snippet) is_expected.to be_disallowed(:read_personal_snippet)
is_expected.to be_disallowed(:comment_personal_snippet) is_expected.to be_disallowed(:comment_personal_snippet)
is_expected.to be_disallowed(:award_emoji)
is_expected.to be_disallowed(*author_permissions) is_expected.to be_disallowed(*author_permissions)
end end
end end
...@@ -91,6 +97,7 @@ describe PersonalSnippetPolicy do ...@@ -91,6 +97,7 @@ describe PersonalSnippetPolicy do
it do it do
is_expected.to be_allowed(:read_personal_snippet) is_expected.to be_allowed(:read_personal_snippet)
is_expected.to be_allowed(:comment_personal_snippet) is_expected.to be_allowed(:comment_personal_snippet)
is_expected.to be_allowed(:award_emoji)
is_expected.to be_allowed(*author_permissions) is_expected.to be_allowed(*author_permissions)
end end
end end
...@@ -105,6 +112,7 @@ describe PersonalSnippetPolicy do ...@@ -105,6 +112,7 @@ describe PersonalSnippetPolicy do
it do it do
is_expected.to be_disallowed(:read_personal_snippet) is_expected.to be_disallowed(:read_personal_snippet)
is_expected.to be_disallowed(:comment_personal_snippet) is_expected.to be_disallowed(:comment_personal_snippet)
is_expected.to be_disallowed(:award_emoji)
is_expected.to be_disallowed(*author_permissions) is_expected.to be_disallowed(*author_permissions)
end end
end end
...@@ -115,6 +123,7 @@ describe PersonalSnippetPolicy do ...@@ -115,6 +123,7 @@ describe PersonalSnippetPolicy do
it do it do
is_expected.to be_disallowed(:read_personal_snippet) is_expected.to be_disallowed(:read_personal_snippet)
is_expected.to be_disallowed(:comment_personal_snippet) is_expected.to be_disallowed(:comment_personal_snippet)
is_expected.to be_disallowed(:award_emoji)
is_expected.to be_disallowed(*author_permissions) is_expected.to be_disallowed(*author_permissions)
end end
end end
...@@ -125,6 +134,7 @@ describe PersonalSnippetPolicy do ...@@ -125,6 +134,7 @@ describe PersonalSnippetPolicy do
it do it do
is_expected.to be_disallowed(:read_personal_snippet) is_expected.to be_disallowed(:read_personal_snippet)
is_expected.to be_disallowed(:comment_personal_snippet) is_expected.to be_disallowed(:comment_personal_snippet)
is_expected.to be_disallowed(:award_emoji)
is_expected.to be_disallowed(*author_permissions) is_expected.to be_disallowed(*author_permissions)
end end
end end
...@@ -135,6 +145,7 @@ describe PersonalSnippetPolicy do ...@@ -135,6 +145,7 @@ describe PersonalSnippetPolicy do
it do it do
is_expected.to be_allowed(:read_personal_snippet) is_expected.to be_allowed(:read_personal_snippet)
is_expected.to be_allowed(:comment_personal_snippet) is_expected.to be_allowed(:comment_personal_snippet)
is_expected.to be_allowed(:award_emoji)
is_expected.to be_allowed(*author_permissions) is_expected.to be_allowed(*author_permissions)
end end
end end
......
...@@ -14,7 +14,8 @@ describe ProjectPolicy do ...@@ -14,7 +14,8 @@ describe ProjectPolicy do
read_project read_board read_list read_wiki read_issue read_project read_board read_list read_wiki read_issue
read_project_for_iids read_issue_iid read_merge_request_iid read_label read_project_for_iids read_issue_iid read_merge_request_iid read_label
read_milestone read_project_snippet read_project_member read_note read_milestone read_project_snippet read_project_member read_note
create_project create_issue create_note upload_file create_project create_issue create_note upload_file create_merge_request_in
award_emoji
] ]
end end
...@@ -35,7 +36,7 @@ describe ProjectPolicy do ...@@ -35,7 +36,7 @@ describe ProjectPolicy do
%i[ %i[
admin_milestone admin_merge_request update_merge_request create_commit_status 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 create_wiki push_code update_pipeline create_merge_request_from create_wiki push_code
resolve_note create_container_image update_container_image resolve_note create_container_image update_container_image
create_environment create_deployment create_environment create_deployment
] ]
...@@ -43,7 +44,7 @@ describe ProjectPolicy do ...@@ -43,7 +44,7 @@ describe ProjectPolicy do
let(:base_master_permissions) do let(:base_master_permissions) do
%i[ %i[
delete_protected_branch update_project_snippet update_environment push_to_delete_protected_branch update_project_snippet update_environment
update_deployment admin_project_snippet update_deployment admin_project_snippet
admin_project_member admin_note admin_wiki admin_project admin_project_member admin_note admin_wiki admin_project
admin_commit_status admin_build admin_container_image admin_commit_status admin_build admin_container_image
...@@ -136,13 +137,66 @@ describe ProjectPolicy do ...@@ -136,13 +137,66 @@ describe ProjectPolicy do
end end
end end
context 'merge requests feature' do
subject { described_class.new(owner, project) }
it 'disallows all permissions when the feature is disabled' do
project.project_feature.update(merge_requests_access_level: ProjectFeature::DISABLED)
mr_permissions = [:create_merge_request_from, :read_merge_request,
:update_merge_request, :admin_merge_request,
:create_merge_request_in]
expect_disallowed(*mr_permissions)
end
end
shared_examples 'archived project policies' do
let(:feature_write_abilities) do
described_class::READONLY_FEATURES_WHEN_ARCHIVED.flat_map do |feature|
described_class.create_update_admin_destroy(feature)
end
end
let(:other_write_abilities) do
%i[
create_merge_request_in
create_merge_request_from
push_to_delete_protected_branch
push_code
request_access
upload_file
resolve_note
award_emoji
]
end
context 'when the project is archived' do
before do
project.archived = true
end
it 'disables write actions on all relevant project features' do
expect_disallowed(*feature_write_abilities)
end
it 'disables some other important write actions' do
expect_disallowed(*other_write_abilities)
end
it 'does not disable other other abilities' do
expect_allowed(*(regular_abilities - feature_write_abilities - other_write_abilities))
end
end
end
shared_examples 'project policies as anonymous' do shared_examples 'project policies as anonymous' do
context 'abilities for public projects' do context 'abilities for public projects' do
context 'when a project has pending invites' do context 'when a project has pending invites' do
let(:group) { create(:group, :public) } let(:group) { create(:group, :public) }
let(:project) { create(:project, :public, namespace: group) } let(:project) { create(:project, :public, namespace: group) }
let(:user_permissions) { [:create_project, :create_issue, :create_note, :upload_file] } let(:user_permissions) { [:create_merge_request_in, :create_project, :create_issue, :create_note, :upload_file, :award_emoji] }
let(:anonymous_permissions) { guest_permissions - user_permissions } let(:anonymous_permissions) { guest_permissions - user_permissions }
subject { described_class.new(nil, project) } subject { described_class.new(nil, project) }
...@@ -154,6 +208,10 @@ describe ProjectPolicy do ...@@ -154,6 +208,10 @@ describe ProjectPolicy do
expect_allowed(*anonymous_permissions) expect_allowed(*anonymous_permissions)
expect_disallowed(*user_permissions) expect_disallowed(*user_permissions)
end end
it_behaves_like 'archived project policies' do
let(:regular_abilities) { anonymous_permissions }
end
end end
end end
...@@ -184,6 +242,10 @@ describe ProjectPolicy do ...@@ -184,6 +242,10 @@ describe ProjectPolicy do
expect_disallowed(*owner_permissions) expect_disallowed(*owner_permissions)
end end
it_behaves_like 'archived project policies' do
let(:regular_abilities) { guest_permissions }
end
context 'public builds enabled' do context 'public builds enabled' do
it do it do
expect_allowed(*guest_permissions) expect_allowed(*guest_permissions)
...@@ -224,12 +286,15 @@ describe ProjectPolicy do ...@@ -224,12 +286,15 @@ describe ProjectPolicy do
it do it do
expect_allowed(*guest_permissions) expect_allowed(*guest_permissions)
expect_allowed(*reporter_permissions) expect_allowed(*reporter_permissions)
expect_allowed(*reporter_permissions)
expect_allowed(*team_member_reporter_permissions) expect_allowed(*team_member_reporter_permissions)
expect_disallowed(*developer_permissions) expect_disallowed(*developer_permissions)
expect_disallowed(*master_permissions) expect_disallowed(*master_permissions)
expect_disallowed(*owner_permissions) expect_disallowed(*owner_permissions)
end end
it_behaves_like 'archived project policies' do
let(:regular_abilities) { reporter_permissions }
end
end end
end end
...@@ -247,6 +312,10 @@ describe ProjectPolicy do ...@@ -247,6 +312,10 @@ describe ProjectPolicy do
expect_disallowed(*master_permissions) expect_disallowed(*master_permissions)
expect_disallowed(*owner_permissions) expect_disallowed(*owner_permissions)
end end
it_behaves_like 'archived project policies' do
let(:regular_abilities) { developer_permissions }
end
end end
end end
...@@ -264,6 +333,10 @@ describe ProjectPolicy do ...@@ -264,6 +333,10 @@ describe ProjectPolicy do
expect_allowed(*master_permissions) expect_allowed(*master_permissions)
expect_disallowed(*owner_permissions) expect_disallowed(*owner_permissions)
end end
it_behaves_like 'archived project policies' do
let(:regular_abilities) { master_permissions }
end
end end
end end
...@@ -281,6 +354,10 @@ describe ProjectPolicy do ...@@ -281,6 +354,10 @@ describe ProjectPolicy do
expect_allowed(*master_permissions) expect_allowed(*master_permissions)
expect_allowed(*owner_permissions) expect_allowed(*owner_permissions)
end end
it_behaves_like 'archived project policies' do
let(:regular_abilities) { owner_permissions }
end
end end
end end
...@@ -298,6 +375,10 @@ describe ProjectPolicy do ...@@ -298,6 +375,10 @@ describe ProjectPolicy do
expect_allowed(*master_permissions) expect_allowed(*master_permissions)
expect_allowed(*owner_permissions) expect_allowed(*owner_permissions)
end end
it_behaves_like 'archived project policies' do
let(:regular_abilities) { owner_permissions }
end
end end
end end
......
...@@ -861,7 +861,7 @@ describe API::MergeRequests do ...@@ -861,7 +861,7 @@ describe API::MergeRequests do
expect(json_response['title']).to eq('Test merge_request') expect(json_response['title']).to eq('Test merge_request')
end end
it 'returns 422 when target project has disabled merge requests' do it 'returns 403 when target project has disabled merge requests' do
project.project_feature.update(merge_requests_access_level: 0) project.project_feature.update(merge_requests_access_level: 0)
post api("/projects/#{forked_project.id}/merge_requests", user2), post api("/projects/#{forked_project.id}/merge_requests", user2),
...@@ -871,7 +871,7 @@ describe API::MergeRequests do ...@@ -871,7 +871,7 @@ describe API::MergeRequests do
author: user2, author: user2,
target_project_id: project.id target_project_id: project.id
expect(response).to have_gitlab_http_status(422) expect(response).to have_gitlab_http_status(403)
end end
it "returns 400 when source_branch is missing" do it "returns 400 when source_branch is missing" do
......
...@@ -340,7 +340,7 @@ describe API::MergeRequests do ...@@ -340,7 +340,7 @@ describe API::MergeRequests do
expect(json_response['title']).to eq('Test merge_request') expect(json_response['title']).to eq('Test merge_request')
end end
it "returns 422 when target project has disabled merge requests" do it "returns 403 when target project has disabled merge requests" do
project.project_feature.update(merge_requests_access_level: 0) project.project_feature.update(merge_requests_access_level: 0)
post v3_api("/projects/#{forked_project.id}/merge_requests", user2), post v3_api("/projects/#{forked_project.id}/merge_requests", user2),
...@@ -350,7 +350,7 @@ describe API::MergeRequests do ...@@ -350,7 +350,7 @@ describe API::MergeRequests do
author: user2, author: user2,
target_project_id: project.id target_project_id: project.id
expect(response).to have_gitlab_http_status(422) expect(response).to have_gitlab_http_status(403)
end end
it "returns 400 when source_branch is missing" do it "returns 400 when source_branch is missing" do
......
require 'spec_helper' require 'spec_helper'
describe MergeRequests::CreateService do describe MergeRequests::CreateService do
include ProjectForksHelper
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:assignee) { create(:user) } let(:assignee) { create(:user) }
...@@ -300,7 +302,7 @@ describe MergeRequests::CreateService do ...@@ -300,7 +302,7 @@ describe MergeRequests::CreateService do
end end
context 'when source and target projects are different' do context 'when source and target projects are different' do
let(:target_project) { create(:project) } let(:target_project) { fork_project(project, nil, repository: true) }
let(:opts) do let(:opts) do
{ {
...@@ -334,6 +336,26 @@ describe MergeRequests::CreateService do ...@@ -334,6 +336,26 @@ describe MergeRequests::CreateService do
.to raise_error Gitlab::Access::AccessDeniedError .to raise_error Gitlab::Access::AccessDeniedError
end end
end end
context 'when the user has access to both projects' do
before do
target_project.add_developer(user)
project.add_developer(user)
end
it 'creates the merge request' do
merge_request = described_class.new(project, user, opts).execute
expect(merge_request).to be_persisted
end
it 'does not create the merge request when the target project is archived' do
target_project.update!(archived: true)
expect { described_class.new(project, user, opts).execute }
.to raise_error Gitlab::Access::AccessDeniedError
end
end
end end
context 'when user sets source project id' do context 'when user sets source project id' do
......
RSpec::Matchers.define :have_emoji do |emoji_name|
match do |actual|
expect(actual).to have_selector("gl-emoji[data-name='#{emoji_name}']")
end
end
...@@ -8,7 +8,8 @@ describe 'projects/buttons/_dropdown' do ...@@ -8,7 +8,8 @@ describe 'projects/buttons/_dropdown' do
assign(:project, project) assign(:project, project)
allow(view).to receive(:current_user).and_return(user) allow(view).to receive(:current_user).and_return(user)
allow(view).to receive(:can?).and_return(true) allow(view).to receive(:can?).with(user, :push_code, project).and_return(true)
allow(view).to receive(:can_collaborate_with_project?).and_return(true)
end end
context 'empty repository' do context 'empty repository' do
......
...@@ -7,6 +7,7 @@ describe 'projects/commit/_commit_box.html.haml' do ...@@ -7,6 +7,7 @@ describe 'projects/commit/_commit_box.html.haml' do
before do before do
assign(:project, project) assign(:project, project)
assign(:commit, project.commit) assign(:commit, project.commit)
allow(view).to receive(:current_user).and_return(user)
allow(view).to receive(:can_collaborate_with_project?).and_return(false) allow(view).to receive(:can_collaborate_with_project?).and_return(false)
end end
...@@ -47,7 +48,8 @@ describe 'projects/commit/_commit_box.html.haml' do ...@@ -47,7 +48,8 @@ describe 'projects/commit/_commit_box.html.haml' do
context 'viewing a commit' do context 'viewing a commit' do
context 'as a developer' do context 'as a developer' do
before do before do
expect(view).to receive(:can_collaborate_with_project?).and_return(true) project.add_developer(user)
allow(view).to receive(:can_collaborate_with_project?).and_return(true)
end end
it 'has a link to create a new tag' do it 'has a link to create a new tag' do
...@@ -58,10 +60,6 @@ describe 'projects/commit/_commit_box.html.haml' do ...@@ -58,10 +60,6 @@ describe 'projects/commit/_commit_box.html.haml' do
end end
context 'as a non-developer' do context 'as a non-developer' do
before do
expect(view).to receive(:can_collaborate_with_project?).and_return(false)
end
it 'does not have a link to create a new tag' do it 'does not have a link to create a new tag' do
render render
......
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