Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRobert Speicher <robert@gitlab.com>2018-04-12 20:04:08 +0300
committerRobert Speicher <robert@gitlab.com>2018-04-12 20:04:08 +0300
commitf5a3d8f75de50f0ac6a14e77c672da79df85866e (patch)
treeb0d3655e4a738867696e82cee07309b17e175531
parent64f91be000bc2963a48851db4dcb0a6bbd2b57bc (diff)
parenteae2ed3366ac13832c56aefe965513b986d7e8b1 (diff)
Merge branch 'dm-archived-read-only' into 'master'
Make archived projects completely read-only Closes #44788 See merge request gitlab-org/gitlab-ce!18136
-rw-r--r--app/assets/javascripts/notes/components/comment_form.vue4
-rw-r--r--app/assets/javascripts/notes/components/note_actions.vue9
-rw-r--r--app/assets/javascripts/notes/components/note_awards_list.vue11
-rw-r--r--app/assets/javascripts/notes/components/note_body.vue1
-rw-r--r--app/assets/javascripts/notes/components/noteable_note.vue1
-rw-r--r--app/controllers/concerns/checks_collaboration.rb21
-rw-r--r--app/controllers/projects/application_controller.rb14
-rw-r--r--app/controllers/projects/issues_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests/creations_controller.rb2
-rw-r--r--app/finders/merge_request_target_project_finder.rb1
-rw-r--r--app/helpers/blob_helper.rb6
-rw-r--r--app/helpers/commits_helper.rb2
-rw-r--r--app/helpers/compare_helper.rb2
-rw-r--r--app/helpers/issues_helper.rb15
-rw-r--r--app/helpers/merge_requests_helper.rb12
-rw-r--r--app/helpers/notes_helper.rb4
-rw-r--r--app/models/ability.rb4
-rw-r--r--app/models/concerns/awardable.rb14
-rw-r--r--app/policies/ci/build_policy.rb4
-rw-r--r--app/policies/ci/pipeline_schedule_policy.rb14
-rw-r--r--app/policies/issuable_policy.rb2
-rw-r--r--app/policies/note_policy.rb11
-rw-r--r--app/policies/personal_snippet_policy.rb2
-rw-r--r--app/policies/project_policy.rb96
-rw-r--r--app/policies/project_policy/class_methods.rb19
-rw-r--r--app/presenters/merge_request_presenter.rb11
-rw-r--r--app/serializers/issue_entity.rb4
-rw-r--r--app/serializers/note_entity.rb6
-rw-r--r--app/services/merge_requests/create_service.rb4
-rw-r--r--app/views/admin/projects/show.html.haml2
-rw-r--r--app/views/award_emoji/_awards_block.html.haml4
-rw-r--r--app/views/layouts/header/_new_dropdown.haml4
-rw-r--r--app/views/projects/_last_push.html.haml7
-rw-r--r--app/views/projects/branches/_branch.html.haml4
-rw-r--r--app/views/projects/buttons/_dropdown.html.haml22
-rw-r--r--app/views/projects/clusters/_empty_state.html.haml5
-rw-r--r--app/views/projects/commit/_commit_box.html.haml11
-rw-r--r--app/views/projects/commit/show.html.haml2
-rw-r--r--app/views/projects/edit.html.haml11
-rw-r--r--app/views/projects/issues/_nav_btns.html.haml13
-rw-r--r--app/views/projects/issues/_new_branch.html.haml8
-rw-r--r--app/views/projects/issues/show.html.haml13
-rw-r--r--app/views/projects/merge_requests/index.html.haml2
-rw-r--r--app/views/projects/notes/_actions.html.haml2
-rw-r--r--app/views/projects/show.html.haml2
-rw-r--r--app/views/projects/tags/_tag.html.haml6
-rw-r--r--app/views/projects/tags/show.html.haml2
-rw-r--r--app/views/projects/tree/_tree_header.html.haml33
-rw-r--r--app/views/shared/_label.html.haml26
-rw-r--r--app/views/shared/milestones/_sidebar.html.haml2
-rw-r--r--app/views/shared/notes/_note.html.haml2
-rw-r--r--db/fixtures/development/10_merge_requests.rb8
-rw-r--r--doc/user/project/settings/index.md15
-rw-r--r--lib/api/merge_requests.rb2
-rw-r--r--lib/api/v3/merge_requests.rb2
-rw-r--r--lib/gitlab/email/handler/create_merge_request_handler.rb3
-rw-r--r--lib/gitlab/user_access.rb2
-rw-r--r--spec/controllers/concerns/checks_collaboration_spec.rb55
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb18
-rw-r--r--spec/factories/award_emoji.rb4
-rw-r--r--spec/features/admin/admin_broadcast_messages_spec.rb2
-rw-r--r--spec/features/groups/show_spec.rb2
-rw-r--r--spec/features/merge_request/user_awards_emoji_spec.rb8
-rw-r--r--spec/features/merge_request/user_cherry_picks_spec.rb8
-rw-r--r--spec/features/projects/awards/user_interacts_with_awards_in_issue_spec.rb70
-rw-r--r--spec/features/projects/branches_spec.rb22
-rw-r--r--spec/features/projects/commit/cherry_pick_spec.rb11
-rw-r--r--spec/features/projects/commit/user_reverts_commit_spec.rb17
-rw-r--r--spec/features/projects/files/user_edits_files_spec.rb23
-rw-r--r--spec/features/projects/issues/user_views_issue_spec.rb18
-rw-r--r--spec/features/projects/merge_request_button_spec.rb12
-rw-r--r--spec/features/projects/merge_requests/user_reverts_merge_request_spec.rb8
-rw-r--r--spec/features/projects/merge_requests/user_views_open_merge_requests_spec.rb12
-rw-r--r--spec/features/projects/show/user_sees_collaboration_links_spec.rb87
-rw-r--r--spec/finders/merge_request_target_project_finder_spec.rb6
-rw-r--r--spec/helpers/issues_helper_spec.rb45
-rw-r--r--spec/javascripts/notes/components/note_actions_spec.js4
-rw-r--r--spec/javascripts/notes/components/note_awards_list_spec.js34
-rw-r--r--spec/javascripts/notes/components/note_body_spec.js1
-rw-r--r--spec/javascripts/notes/mock_data.js15
-rw-r--r--spec/models/ability_spec.rb56
-rw-r--r--spec/models/concerns/awardable_spec.rb25
-rw-r--r--spec/policies/note_policy_spec.rb4
-rw-r--r--spec/policies/personal_snippet_policy_spec.rb11
-rw-r--r--spec/policies/project_policy_spec.rb93
-rw-r--r--spec/requests/api/merge_requests_spec.rb4
-rw-r--r--spec/requests/api/v3/merge_requests_spec.rb4
-rw-r--r--spec/services/merge_requests/create_service_spec.rb24
-rw-r--r--spec/support/matchers/have_emoji.rb5
-rw-r--r--spec/views/projects/buttons/_dropdown.html.haml_spec.rb3
-rw-r--r--spec/views/projects/commit/_commit_box.html.haml_spec.rb8
91 files changed, 935 insertions, 297 deletions
diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue
index 648fa6ff804..396a675b4ac 100644
--- a/app/assets/javascripts/notes/components/comment_form.vue
+++ b/app/assets/javascripts/notes/components/comment_form.vue
@@ -317,10 +317,10 @@ Please check your network connection and try again.`;
<note-signed-out-widget v-if="!isLoggedIn" />
<discussion-locked-widget
issuable-type="issue"
- v-else-if="!canCreateNote"
+ v-else-if="isLocked(getNoteableData) && !canCreateNote"
/>
<ul
- v-else
+ v-else-if="canCreateNote"
class="notes notes-form timeline">
<li class="timeline-entry">
<div class="timeline-entry-inner">
diff --git a/app/assets/javascripts/notes/components/note_actions.vue b/app/assets/javascripts/notes/components/note_actions.vue
index a7e2d857013..626b0799581 100644
--- a/app/assets/javascripts/notes/components/note_actions.vue
+++ b/app/assets/javascripts/notes/components/note_actions.vue
@@ -40,6 +40,10 @@ export default {
type: Boolean,
required: true,
},
+ canAwardEmoji: {
+ type: Boolean,
+ required: true,
+ },
canDelete: {
type: Boolean,
required: true,
@@ -74,9 +78,6 @@ export default {
shouldShowActionsDropdown() {
return this.currentUserId && (this.canEdit || this.canReportAsAbuse);
},
- canAddAwardEmoji() {
- return this.currentUserId;
- },
isAuthoredByCurrentUser() {
return this.authorId === this.currentUserId;
},
@@ -149,7 +150,7 @@ export default {
</button>
</div>
<div
- v-if="canAddAwardEmoji"
+ v-if="canAwardEmoji"
class="note-actions-item">
<a
v-tooltip
diff --git a/app/assets/javascripts/notes/components/note_awards_list.vue b/app/assets/javascripts/notes/components/note_awards_list.vue
index 6cb8229e268..e8fd155a1ee 100644
--- a/app/assets/javascripts/notes/components/note_awards_list.vue
+++ b/app/assets/javascripts/notes/components/note_awards_list.vue
@@ -28,6 +28,10 @@ export default {
type: Number,
required: true,
},
+ canAwardEmoji: {
+ type: Boolean,
+ required: true,
+ },
},
computed: {
...mapGetters(['getUserData']),
@@ -67,9 +71,6 @@ export default {
isAuthoredByMe() {
return this.noteAuthorId === this.getUserData.id;
},
- isLoggedIn() {
- return this.getUserData.id;
- },
},
created() {
this.emojiSmiling = emojiSmiling;
@@ -156,7 +157,7 @@ export default {
return title;
},
handleAward(awardName) {
- if (!this.isLoggedIn) {
+ if (!this.canAwardEmoji) {
return;
}
@@ -208,7 +209,7 @@ export default {
</span>
</button>
<div
- v-if="isLoggedIn"
+ v-if="canAwardEmoji"
class="award-menu-holder">
<button
v-tooltip
diff --git a/app/assets/javascripts/notes/components/note_body.vue b/app/assets/javascripts/notes/components/note_body.vue
index 069f94c5845..0cb626c14f4 100644
--- a/app/assets/javascripts/notes/components/note_body.vue
+++ b/app/assets/javascripts/notes/components/note_body.vue
@@ -112,6 +112,7 @@ export default {
:note-author-id="note.author.id"
:awards="note.award_emoji"
:toggle-award-path="note.toggle_award_path"
+ :can-award-emoji="note.current_user.can_award_emoji"
/>
<note-attachment
v-if="note.attachment"
diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue
index 3554027d2b4..566f5c68e66 100644
--- a/app/assets/javascripts/notes/components/noteable_note.vue
+++ b/app/assets/javascripts/notes/components/noteable_note.vue
@@ -177,6 +177,7 @@ export default {
:note-id="note.id"
:access-level="note.human_access"
:can-edit="note.current_user.can_edit"
+ :can-award-emoji="note.current_user.can_award_emoji"
:can-delete="note.current_user.can_edit"
:can-report-as-abuse="canReportAsAbuse"
:report-abuse-path="note.report_abuse_path"
diff --git a/app/controllers/concerns/checks_collaboration.rb b/app/controllers/concerns/checks_collaboration.rb
new file mode 100644
index 00000000000..81367663a06
--- /dev/null
+++ b/app/controllers/concerns/checks_collaboration.rb
@@ -0,0 +1,21 @@
+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
diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb
index 6d9b42a2c04..032bb2267e7 100644
--- a/app/controllers/projects/application_controller.rb
+++ b/app/controllers/projects/application_controller.rb
@@ -1,5 +1,6 @@
class Projects::ApplicationController < ApplicationController
include RoutableActions
+ include ChecksCollaboration
skip_before_action :authenticate_user!
before_action :project
@@ -31,14 +32,6 @@ class Projects::ApplicationController < ApplicationController
@repository ||= project.repository
end
- def can_collaborate_with_project?(project = nil, ref: nil)
- project ||= @project
-
- can?(current_user, :push_code, project) ||
- (current_user && current_user.already_forked?(project)) ||
- user_access(project).can_push_to_branch?(ref)
- end
-
def authorize_action!(action)
unless can?(current_user, action, project)
return access_denied!
@@ -91,9 +84,4 @@ class Projects::ApplicationController < ApplicationController
def check_issues_available!
return render_404 unless @project.feature_available?(:issues, current_user)
end
-
- def user_access(project)
- @user_access ||= {}
- @user_access[project] ||= Gitlab::UserAccess.new(current_user, project: project)
- end
end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index b14939c4216..767e492f566 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -20,7 +20,7 @@ class Projects::IssuesController < Projects::ApplicationController
before_action :authorize_update_issuable!, only: [:edit, :update, :move]
# Allow create a new branch and empty WIP merge request from current issue
- before_action :authorize_create_merge_request!, only: [:create_merge_request]
+ before_action :authorize_create_merge_request_from!, only: [:create_merge_request]
respond_to :html
diff --git a/app/controllers/projects/merge_requests/creations_controller.rb b/app/controllers/projects/merge_requests/creations_controller.rb
index a90030a8312..4a377fefc62 100644
--- a/app/controllers/projects/merge_requests/creations_controller.rb
+++ b/app/controllers/projects/merge_requests/creations_controller.rb
@@ -5,7 +5,7 @@ class Projects::MergeRequests::CreationsController < Projects::MergeRequests::Ap
skip_before_action :merge_request
before_action :whitelist_query_limiting, only: [:create]
- before_action :authorize_create_merge_request!
+ before_action :authorize_create_merge_request_from!
before_action :apply_diff_view_cookie!, only: [:diffs, :diff_for_path]
before_action :build_merge_request, except: [:create]
diff --git a/app/finders/merge_request_target_project_finder.rb b/app/finders/merge_request_target_project_finder.rb
index f358938344e..188ec447a94 100644
--- a/app/finders/merge_request_target_project_finder.rb
+++ b/app/finders/merge_request_target_project_finder.rb
@@ -12,6 +12,7 @@ class MergeRequestTargetProjectFinder
if @source_project.fork_network
@source_project.fork_network.projects
.public_or_visible_to_user(current_user)
+ .non_archived
.with_feature_available_for_user(:merge_requests, current_user)
else
Project.where(id: source_project)
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 2b440e4d584..866b8773db6 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -59,7 +59,7 @@ module BlobHelper
button_tag label, class: "#{common_classes} disabled has-tooltip", title: "It is not possible to #{action} files that are stored in LFS using the web interface", data: { container: 'body' }
elsif can_modify_blob?(blob, project, ref)
button_tag label, class: "#{common_classes}", 'data-target' => "#modal-#{modal_type}-blob", 'data-toggle' => 'modal'
- elsif can?(current_user, :fork_project, project)
+ elsif can?(current_user, :fork_project, project) && can?(current_user, :create_merge_request_in, project)
edit_fork_button_tag(common_classes, project, label, edit_modify_file_fork_params(action), action)
end
end
@@ -280,7 +280,7 @@ module BlobHelper
options << link_to("submit an issue", new_project_issue_path(project))
end
- merge_project = can?(current_user, :create_merge_request, project) ? project : (current_user && current_user.fork_of(project))
+ merge_project = merge_request_source_project_for_project(@project)
if merge_project
options << link_to("create a merge request", project_new_merge_request_path(project))
end
@@ -334,7 +334,7 @@ module BlobHelper
# Web IDE (Beta) requires the user to have this feature enabled
elsif !current_user || (current_user && can_modify_blob?(blob, project, ref))
edit_link_tag(text, edit_path, common_classes)
- elsif current_user && can?(current_user, :fork_project, project)
+ elsif can?(current_user, :fork_project, project) && can?(current_user, :create_merge_request_in, project)
edit_fork_button_tag(common_classes, project, text, edit_blob_fork_params(edit_path))
end
end
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index 7cc56de24e4..98894b86551 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -163,7 +163,7 @@ module CommitsHelper
tooltip = "#{action.capitalize} this #{commit.change_type_title(current_user)} in a new merge request" if has_tooltip
btn_class = "btn btn-#{btn_class}" unless btn_class.nil?
- if can_collaborate_with_project?
+ if can_collaborate_with_project?(@project)
link_to action.capitalize, "#modal-#{action}-commit", 'data-toggle' => 'modal', 'data-container' => 'body', title: (tooltip if has_tooltip), class: "#{btn_class} #{'has-tooltip' if has_tooltip}"
elsif can?(current_user, :fork_project, @project)
continue_params = {
diff --git a/app/helpers/compare_helper.rb b/app/helpers/compare_helper.rb
index 8bf96c0905f..2df5b5d1695 100644
--- a/app/helpers/compare_helper.rb
+++ b/app/helpers/compare_helper.rb
@@ -3,7 +3,7 @@ module CompareHelper
from.present? &&
to.present? &&
from != to &&
- can?(current_user, :create_merge_request, project) &&
+ can?(current_user, :create_merge_request_from, project) &&
project.repository.branch_exists?(from) &&
project.repository.branch_exists?(to)
end
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index 0f25d401406..96dc7ae1185 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -82,8 +82,8 @@ module IssuesHelper
names.to_sentence
end
- def award_state_class(awards, current_user)
- if !current_user
+ def award_state_class(awardable, awards, current_user)
+ if !can?(current_user, :award_emoji, awardable)
"disabled"
elsif current_user && awards.find { |a| a.user_id == current_user.id }
"active"
@@ -126,6 +126,17 @@ module IssuesHelper
link_to link_text, path
end
+ def show_new_issue_link?(project)
+ return false unless project
+ return false if project.archived?
+
+ # We want to show the link to users that are not signed in, that way they
+ # get directed to the sign-in/sign-up flow and afterwards to the new issue page.
+ return true unless current_user
+
+ can?(current_user, :create_issue, project)
+ end
+
# Required for Banzai::Filter::IssueReferenceFilter
module_function :url_for_issue
module_function :url_for_internal_issue
diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb
index fb4fe1c40b7..c19c5b9cc82 100644
--- a/app/helpers/merge_requests_helper.rb
+++ b/app/helpers/merge_requests_helper.rb
@@ -138,6 +138,18 @@ module MergeRequestsHelper
end
end
+ def merge_request_source_project_for_project(project = @project)
+ unless can?(current_user, :create_merge_request_in, project)
+ return nil
+ end
+
+ if can?(current_user, :create_merge_request_from, project)
+ project
+ else
+ current_user.fork_of(project)
+ end
+ end
+
def merge_params_ee(merge_request)
{}
end
diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb
index 27ed48fdbc7..7f67574a428 100644
--- a/app/helpers/notes_helper.rb
+++ b/app/helpers/notes_helper.rb
@@ -6,10 +6,6 @@ module NotesHelper
end
end
- def note_editable?(note)
- Ability.can_edit_note?(current_user, note)
- end
-
def note_supports_quick_actions?(note)
Notes::QuickActionsService.supported?(note)
end
diff --git a/app/models/ability.rb b/app/models/ability.rb
index 6dae49f38dc..618d4af4272 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -46,10 +46,6 @@ class Ability
end
end
- def can_edit_note?(user, note)
- allowed?(user, :edit_note, note)
- end
-
def allowed?(user, action, subject = :global, opts = {})
if subject.is_a?(Hash)
opts, subject = subject, :global
diff --git a/app/models/concerns/awardable.rb b/app/models/concerns/awardable.rb
index d8394415362..fce37e7f78e 100644
--- a/app/models/concerns/awardable.rb
+++ b/app/models/concerns/awardable.rb
@@ -79,11 +79,7 @@ module Awardable
end
def user_can_award?(current_user, name)
- if user_authored?(current_user)
- !awardable_votes?(normalize_name(name))
- else
- true
- end
+ awardable_by_user?(current_user, name) && Ability.allowed?(current_user, :award_emoji, self)
end
def user_authored?(current_user)
@@ -119,4 +115,12 @@ module Awardable
def normalize_name(name)
Gitlab::Emoji.normalize_emoji_name(name)
end
+
+ def awardable_by_user?(current_user, name)
+ if user_authored?(current_user)
+ !awardable_votes?(normalize_name(name))
+ else
+ true
+ end
+ end
end
diff --git a/app/policies/ci/build_policy.rb b/app/policies/ci/build_policy.rb
index 1ab391a5a9d..808a81cbbf9 100644
--- a/app/policies/ci/build_policy.rb
+++ b/app/policies/ci/build_policy.rb
@@ -11,7 +11,7 @@ module Ci
end
condition(:owner_of_job) do
- can?(:developer_access) && @subject.triggered_by?(@user)
+ @subject.triggered_by?(@user)
end
rule { protected_ref }.policy do
@@ -19,6 +19,6 @@ module Ci
prevent :erase_build
end
- rule { can?(:master_access) | owner_of_job }.enable :erase_build
+ rule { can?(:admin_build) | (can?(:update_build) & owner_of_job) }.enable :erase_build
end
end
diff --git a/app/policies/ci/pipeline_schedule_policy.rb b/app/policies/ci/pipeline_schedule_policy.rb
index dc7a4aed577..ecba0488d3c 100644
--- a/app/policies/ci/pipeline_schedule_policy.rb
+++ b/app/policies/ci/pipeline_schedule_policy.rb
@@ -7,23 +7,17 @@ module Ci
end
condition(:owner_of_schedule) do
- can?(:developer_access) && pipeline_schedule.owned_by?(@user)
+ pipeline_schedule.owned_by?(@user)
end
- condition(:non_owner_of_schedule) do
- !pipeline_schedule.owned_by?(@user)
- end
-
- rule { can?(:developer_access) }.policy do
- enable :play_pipeline_schedule
- end
+ rule { can?(:create_pipeline) }.enable :play_pipeline_schedule
- rule { can?(:master_access) | owner_of_schedule }.policy do
+ rule { can?(:admin_pipeline) | (can?(:update_build) & owner_of_schedule) }.policy do
enable :update_pipeline_schedule
enable :admin_pipeline_schedule
end
- rule { can?(:master_access) & non_owner_of_schedule }.policy do
+ rule { can?(:admin_pipeline_schedule) & ~owner_of_schedule }.policy do
enable :take_ownership_pipeline_schedule
end
diff --git a/app/policies/issuable_policy.rb b/app/policies/issuable_policy.rb
index e86d1c8f98e..b431d376e3d 100644
--- a/app/policies/issuable_policy.rb
+++ b/app/policies/issuable_policy.rb
@@ -18,9 +18,7 @@ class IssuablePolicy < BasePolicy
rule { locked & ~is_project_member }.policy do
prevent :create_note
- prevent :update_note
prevent :admin_note
prevent :resolve_note
- prevent :edit_note
end
end
diff --git a/app/policies/note_policy.rb b/app/policies/note_policy.rb
index d4cb5a77e63..077a6761ee6 100644
--- a/app/policies/note_policy.rb
+++ b/app/policies/note_policy.rb
@@ -1,26 +1,21 @@
class NotePolicy < BasePolicy
delegate { @subject.project }
- delegate { @subject.noteable if @subject.noteable.lockable? }
+ delegate { @subject.noteable if DeclarativePolicy.has_policy?(@subject.noteable) }
condition(:is_author) { @user && @subject.author == @user }
- condition(:for_merge_request, scope: :subject) { @subject.for_merge_request? }
condition(:is_noteable_author) { @user && @subject.noteable.author_id == @user.id }
condition(:editable, scope: :subject) { @subject.editable? }
- rule { ~editable | anonymous }.prevent :edit_note
-
- rule { is_author | admin }.enable :edit_note
- rule { can?(:master_access) }.enable :edit_note
+ rule { ~editable }.prevent :admin_note
rule { is_author }.policy do
enable :read_note
- enable :update_note
enable :admin_note
enable :resolve_note
end
- rule { for_merge_request & is_noteable_author }.policy do
+ rule { is_noteable_author }.policy do
enable :resolve_note
end
end
diff --git a/app/policies/personal_snippet_policy.rb b/app/policies/personal_snippet_policy.rb
index cac0530b9f7..c1a84727cfa 100644
--- a/app/policies/personal_snippet_policy.rb
+++ b/app/policies/personal_snippet_policy.rb
@@ -25,4 +25,6 @@ class PersonalSnippetPolicy < BasePolicy
end
rule { anonymous }.prevent :comment_personal_snippet
+
+ rule { can?(:comment_personal_snippet) }.enable :award_emoji
end
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index 21bb0934dee..3529d0aa60c 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -1,12 +1,26 @@
class ProjectPolicy < BasePolicy
- def self.create_read_update_admin(name)
- [
- :"create_#{name}",
- :"read_#{name}",
- :"update_#{name}",
- :"admin_#{name}"
- ]
- end
+ extend ClassMethods
+
+ READONLY_FEATURES_WHEN_ARCHIVED = %i[
+ issue
+ list
+ merge_request
+ label
+ milestone
+ project_snippet
+ wiki
+ note
+ pipeline
+ pipeline_schedule
+ build
+ trigger
+ environment
+ deployment
+ commit_status
+ container_image
+ pages
+ cluster
+ ].freeze
desc "User is a project owner"
condition :owner do
@@ -15,7 +29,7 @@ class ProjectPolicy < BasePolicy
end
desc "Project has public builds enabled"
- condition(:public_builds, scope: :subject) { project.public_builds? }
+ condition(:public_builds, scope: :subject, score: 0) { project.public_builds? }
# For guest access we use #team_member? so we can use
# project.members, which gets cached in subject scope.
@@ -35,7 +49,7 @@ class ProjectPolicy < BasePolicy
condition(:master) { team_access_level >= Gitlab::Access::MASTER }
desc "Project is public"
- condition(:public_project, scope: :subject) { project.public? }
+ condition(:public_project, scope: :subject, score: 0) { project.public? }
desc "Project is visible to internal users"
condition(:internal_access) do
@@ -46,7 +60,7 @@ class ProjectPolicy < BasePolicy
condition(:group_member, scope: :subject) { project_group_member? }
desc "Project is archived"
- condition(:archived, scope: :subject) { project.archived? }
+ condition(:archived, scope: :subject, score: 0) { project.archived? }
condition(:default_issues_tracker, scope: :subject) { project.default_issues_tracker? }
@@ -56,10 +70,10 @@ class ProjectPolicy < BasePolicy
end
desc "Project has an external wiki"
- condition(:has_external_wiki, scope: :subject) { project.has_external_wiki? }
+ condition(:has_external_wiki, scope: :subject, score: 0) { project.has_external_wiki? }
desc "Project has request access enabled"
- condition(:request_access_enabled, scope: :subject) { project.request_access_enabled }
+ condition(:request_access_enabled, scope: :subject, score: 0) { project.request_access_enabled }
desc "Has merge requests allowing pushes to user"
condition(:has_merge_requests_allowing_pushes, scope: :subject) do
@@ -126,6 +140,7 @@ class ProjectPolicy < BasePolicy
rule { can?(:guest_access) }.policy do
enable :read_project
+ enable :create_merge_request_in
enable :read_board
enable :read_list
enable :read_wiki
@@ -140,6 +155,7 @@ class ProjectPolicy < BasePolicy
enable :create_note
enable :upload_file
enable :read_cycle_analytics
+ enable :award_emoji
end
# These abilities are not allowed to admins that are not members of the project,
@@ -197,7 +213,7 @@ class ProjectPolicy < BasePolicy
enable :create_pipeline
enable :update_pipeline
enable :create_pipeline_schedule
- enable :create_merge_request
+ enable :create_merge_request_from
enable :create_wiki
enable :push_code
enable :resolve_note
@@ -208,7 +224,7 @@ class ProjectPolicy < BasePolicy
end
rule { can?(:master_access) }.policy do
- enable :delete_protected_branch
+ enable :push_to_delete_protected_branch
enable :update_project_snippet
enable :update_environment
enable :update_deployment
@@ -231,37 +247,50 @@ class ProjectPolicy < BasePolicy
end
rule { archived }.policy do
- prevent :create_merge_request
prevent :push_code
- prevent :delete_protected_branch
- prevent :update_merge_request
- prevent :admin_merge_request
+ prevent :push_to_delete_protected_branch
+ prevent :request_access
+ prevent :upload_file
+ prevent :resolve_note
+ prevent :create_merge_request_from
+ prevent :create_merge_request_in
+ prevent :award_emoji
+
+ READONLY_FEATURES_WHEN_ARCHIVED.each do |feature|
+ prevent(*create_update_admin_destroy(feature))
+ end
+ end
+
+ rule { issues_disabled }.policy do
+ prevent(*create_read_update_admin_destroy(:issue))
end
rule { merge_requests_disabled | repository_disabled }.policy do
- prevent(*create_read_update_admin(:merge_request))
+ prevent :create_merge_request_in
+ prevent :create_merge_request_from
+ prevent(*create_read_update_admin_destroy(:merge_request))
end
rule { issues_disabled & merge_requests_disabled }.policy do
- prevent(*create_read_update_admin(:label))
- prevent(*create_read_update_admin(:milestone))
+ prevent(*create_read_update_admin_destroy(:label))
+ prevent(*create_read_update_admin_destroy(:milestone))
end
rule { snippets_disabled }.policy do
- prevent(*create_read_update_admin(:project_snippet))
+ prevent(*create_read_update_admin_destroy(:project_snippet))
end
rule { wiki_disabled & ~has_external_wiki }.policy do
- prevent(*create_read_update_admin(:wiki))
+ prevent(*create_read_update_admin_destroy(:wiki))
prevent(:download_wiki_code)
end
rule { builds_disabled | repository_disabled }.policy do
- prevent(*create_read_update_admin(:build))
- prevent(*(create_read_update_admin(:pipeline) - [:read_pipeline]))
- prevent(*create_read_update_admin(:pipeline_schedule))
- prevent(*create_read_update_admin(:environment))
- prevent(*create_read_update_admin(:deployment))
+ prevent(*create_update_admin_destroy(:pipeline))
+ prevent(*create_read_update_admin_destroy(:build))
+ prevent(*create_read_update_admin_destroy(:pipeline_schedule))
+ prevent(*create_read_update_admin_destroy(:environment))
+ prevent(*create_read_update_admin_destroy(:deployment))
end
rule { repository_disabled }.policy do
@@ -272,7 +301,7 @@ class ProjectPolicy < BasePolicy
end
rule { container_registry_disabled }.policy do
- prevent(*create_read_update_admin(:container_image))
+ prevent(*create_read_update_admin_destroy(:container_image))
end
rule { anonymous & ~public_project }.prevent_all
@@ -314,13 +343,6 @@ class ProjectPolicy < BasePolicy
enable :read_pipeline_schedule
end
- rule { issues_disabled }.policy do
- prevent :create_issue
- prevent :update_issue
- prevent :admin_issue
- prevent :read_issue
- end
-
# These rules are included to allow maintainers of projects to push to certain
# to run pipelines for the branches they have access to.
rule { can?(:public_access) & has_merge_requests_allowing_pushes }.policy do
diff --git a/app/policies/project_policy/class_methods.rb b/app/policies/project_policy/class_methods.rb
new file mode 100644
index 00000000000..60e5aba00ba
--- /dev/null
+++ b/app/policies/project_policy/class_methods.rb
@@ -0,0 +1,19 @@
+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
diff --git a/app/presenters/merge_request_presenter.rb b/app/presenters/merge_request_presenter.rb
index 9f3f2637183..4b4132af2d0 100644
--- a/app/presenters/merge_request_presenter.rb
+++ b/app/presenters/merge_request_presenter.rb
@@ -3,6 +3,7 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
include GitlabRoutingHelper
include MarkupHelper
include TreeHelper
+ include ChecksCollaboration
include Gitlab::Utils::StrongMemoize
presents :merge_request
@@ -152,11 +153,11 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
end
def can_revert_on_current_merge_request?
- user_can_collaborate_with_project? && cached_can_be_reverted?
+ can_collaborate_with_project?(project) && cached_can_be_reverted?
end
def can_cherry_pick_on_current_merge_request?
- user_can_collaborate_with_project? && can_be_cherry_picked?
+ can_collaborate_with_project?(project) && can_be_cherry_picked?
end
def can_push_to_source_branch?
@@ -195,12 +196,6 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
end.sort.to_sentence
end
- def user_can_collaborate_with_project?
- can?(current_user, :push_code, project) ||
- (current_user && current_user.already_forked?(project)) ||
- can_push_to_source_branch?
- end
-
def user_can_fork_project?
can?(current_user, :fork_project, project)
end
diff --git a/app/serializers/issue_entity.rb b/app/serializers/issue_entity.rb
index b5e2334b6e3..840fdbcbf14 100644
--- a/app/serializers/issue_entity.rb
+++ b/app/serializers/issue_entity.rb
@@ -29,6 +29,10 @@ class IssueEntity < IssuableEntity
expose :can_update do |issue|
can?(request.current_user, :update_issue, issue)
end
+
+ expose :can_award_emoji do |issue|
+ can?(request.current_user, :award_emoji, issue)
+ end
end
expose :create_note_path do |issue|
diff --git a/app/serializers/note_entity.rb b/app/serializers/note_entity.rb
index c964aa9c99b..06d603b277e 100644
--- a/app/serializers/note_entity.rb
+++ b/app/serializers/note_entity.rb
@@ -15,7 +15,11 @@ class NoteEntity < API::Entities::Note
expose :current_user do
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
diff --git a/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb
index c57a2445341..fe1ac70781e 100644
--- a/app/services/merge_requests/create_service.rb
+++ b/app/services/merge_requests/create_service.rb
@@ -71,8 +71,8 @@ module MergeRequests
params.delete(:source_project_id)
params.delete(:target_project_id)
- unless can?(current_user, :read_project, @source_project) &&
- can?(current_user, :read_project, @project)
+ unless can?(current_user, :create_merge_request_from, @source_project) &&
+ can?(current_user, :create_merge_request_in, @project)
raise Gitlab::Access::AccessDeniedError
end
diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml
index c47b8a88f56..aeba9788fda 100644
--- a/app/views/admin/projects/show.html.haml
+++ b/app/views/admin/projects/show.html.haml
@@ -101,7 +101,7 @@
- if @project.archived?
%li
%span.light archived:
- %strong repository is read-only
+ %strong project is read-only
%li
%span.light access:
diff --git a/app/views/award_emoji/_awards_block.html.haml b/app/views/award_emoji/_awards_block.html.haml
index 5f07d2720c2..4b3c52af16a 100644
--- a/app/views/award_emoji/_awards_block.html.haml
+++ b/app/views/award_emoji/_awards_block.html.haml
@@ -3,13 +3,13 @@
.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|
%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) } }
= emoji_icon(emoji)
%span.award-control-text.js-counter
= awards.count
- - if current_user
+ - if can?(current_user, :award_emoji, awardable)
.award-menu-holder.js-award-holder
%button.btn.award-control.has-tooltip.js-add-award{ type: 'button',
'aria-label': 'Add reaction',
diff --git a/app/views/layouts/header/_new_dropdown.haml b/app/views/layouts/header/_new_dropdown.haml
index eb32f393310..6f53f5ac1ae 100644
--- a/app/views/layouts/header/_new_dropdown.haml
+++ b/app/views/layouts/header/_new_dropdown.haml
@@ -19,8 +19,8 @@
%li.dropdown-bold-header GitLab
- if @project&.persisted?
- - create_project_issue = can?(current_user, :create_issue, @project)
- - merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
+ - create_project_issue = show_new_issue_link?(@project)
+ - merge_project = merge_request_source_project_for_project(@project)
- create_project_snippet = can?(current_user, :create_project_snippet, @project)
- if create_project_issue || merge_project || create_project_snippet
%li.dropdown-bold-header This project
diff --git a/app/views/projects/_last_push.html.haml b/app/views/projects/_last_push.html.haml
index 6a1035d2dc7..f6d396c8127 100644
--- a/app/views/projects/_last_push.html.haml
+++ b/app/views/projects/_last_push.html.haml
@@ -13,6 +13,7 @@
#{time_ago_with_tooltip(event.created_at)}
- .flex-right
- = 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') }
+ - if can?(current_user, :create_merge_request_in, event.project.default_merge_request_target)
+ .flex-right
+ = 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') }
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index 883dfb3e6c8..71176acd12d 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -4,7 +4,7 @@
- diverging_commit_counts = @repository.diverging_commit_counts(branch)
- number_commits_behind = diverging_commit_counts[:behind]
- 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}" }
.branch-info
.branch-title
@@ -61,7 +61,7 @@
title: s_('Branches|The default branch cannot be deleted') }
= icon("trash-o")
- 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",
title: s_('Branches|Delete protected branch'),
data: { toggle: "modal",
diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml
index 18e948ce35a..2e86a7d36d7 100644
--- a/app/views/projects/buttons/_dropdown.html.haml
+++ b/app/views/projects/buttons/_dropdown.html.haml
@@ -1,13 +1,17 @@
-- 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
%a.btn.dropdown-toggle.has-tooltip{ href: '#', title: _('Create new...'), 'data-toggle' => 'dropdown', 'data-container' => 'body', 'aria-label' => _('Create new...') }
= icon('plus')
= icon("caret-down")
%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
%li.dropdown-header= _('This project')
@@ -20,17 +24,17 @@
- if can_create_project_snippet
%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')
- - 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')
- unless @project.empty_repo?
%li= link_to _('New branch'), new_project_branch_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')
- - 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'),
notice: edit_in_new_fork_notice,
notice_now: edit_in_new_fork_notice_now }
diff --git a/app/views/projects/clusters/_empty_state.html.haml b/app/views/projects/clusters/_empty_state.html.haml
index 112dde66ff7..5f49d03b1bb 100644
--- a/app/views/projects/clusters/_empty_state.html.haml
+++ b/app/views/projects/clusters/_empty_state.html.haml
@@ -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')
%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
- = link_to s_('ClusterIntegration|Add Kubernetes cluster'), new_project_cluster_path(@project), class: 'btn btn-success'
+ - if can?(current_user, :create_cluster, @project)
+ .text-center
+ = link_to s_('ClusterIntegration|Add Kubernetes cluster'), new_project_cluster_path(@project), class: 'btn btn-success'
diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml
index 74c5317428c..213c4c90a0e 100644
--- a/app/views/projects/commit/_commit_box.html.haml
+++ b/app/views/projects/commit/_commit_box.html.haml
@@ -1,3 +1,5 @@
+- can_collaborate = can_collaborate_with_project?(@project)
+
.page-content-header.js-commit-box{ 'data-commit-path' => branches_project_commit_path(@project, @commit.id) }
.header-main-content
= render partial: 'signature', object: @commit.signature
@@ -32,12 +34,13 @@
%li.visible-xs-block.visible-sm-block
= link_to project_tree_path(@project, @commit) do
#{ _('Browse Files') }
- - unless @commit.has_been_reverted?(current_user)
+ - if can_collaborate && !@commit.has_been_reverted?(current_user)
%li.clearfix
= revert_commit_link(@commit, project_commit_path(@project, @commit.id), has_tooltip: false)
- %li.clearfix
- = cherry_pick_commit_link(@commit, project_commit_path(@project, @commit.id), has_tooltip: false)
- - if can_collaborate_with_project?
+ - if can_collaborate
+ %li.clearfix
+ = cherry_pick_commit_link(@commit, project_commit_path(@project, @commit.id), has_tooltip: false)
+ - if can?(current_user, :push_code, @project)
%li.clearfix
= link_to s_("CreateTag|Tag"), new_project_tag_path(@project, ref: @commit)
%li.divider
diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml
index abb292f8f27..541ae905246 100644
--- a/app/views/projects/commit/show.html.haml
+++ b/app/views/projects/commit/show.html.haml
@@ -17,6 +17,6 @@
.limited-width-notes
= 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|
= render "projects/commit/change", type: type, commit: @commit, title: @commit.title
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index 99eeb9551e3..0994498c6be 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -114,17 +114,18 @@
Archive project
- if @project.archived?
%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.
= 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"
- else
%p
- Archiving the project will mark its repository as read-only. It is hidden from the dashboard and doesn't show up in searches.
- %strong Archived projects cannot be committed to!
+ Archiving the project will make it entirely read-only. It is hidden from the dashboard and doesn't show up in searches.
+ %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),
- 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"
.sub-section.rename-respository
%h4.warning-title
diff --git a/app/views/projects/issues/_nav_btns.html.haml b/app/views/projects/issues/_nav_btns.html.haml
index 0d39edb7bfd..dd1a836fa20 100644
--- a/app/views/projects/issues/_nav_btns.html.haml
+++ b/app/views/projects/issues/_nav_btns.html.haml
@@ -2,9 +2,10 @@
= icon('rss')
- if @can_bulk_update
= button_tag "Edit issues", class: "btn btn-default append-right-10 js-bulk-update-toggle"
-= link_to "New issue", new_project_issue_path(@project,
- issue: { assignee_id: finder.assignee.try(:id),
- milestone_id: finder.milestones.first.try(:id) }),
- class: "btn btn-new",
- title: "New issue",
- id: "new_issue_link"
+- if show_new_issue_link?(@project)
+ = link_to "New issue", new_project_issue_path(@project,
+ issue: { assignee_id: finder.assignee.try(:id),
+ milestone_id: finder.milestones.first.try(:id) }),
+ class: "btn btn-new",
+ title: "New issue",
+ id: "new_issue_link"
diff --git a/app/views/projects/issues/_new_branch.html.haml b/app/views/projects/issues/_new_branch.html.haml
index 36e24037214..4b8bf578b28 100644
--- a/app/views/projects/issues/_new_branch.html.haml
+++ b/app/views/projects/issues/_new_branch.html.haml
@@ -1,8 +1,8 @@
-- 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)
+ - 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)
- 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)
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index ec7e87219f5..f1fc1c2316d 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -7,6 +7,7 @@
- can_update_issue = can?(current_user, :update_issue, @issue)
- can_report_spam = @issue.submittable_as_spam_by?(current_user)
+- can_create_issue = show_new_issue_link?(@project)
.detail-page-header
.detail-page-header-body
@@ -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'
- 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'
- - if can_update_issue || can_report_spam
- %li.divider
- %li= link_to 'New issue', new_project_issue_path(@project), title: 'New issue', id: 'new_issue_link'
+ - if can_create_issue
+ - if can_update_issue || can_report_spam
+ %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
- 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 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
+ - if can_create_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
.detail-page-description.content-block
diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml
index b2c0d9e1cfa..623380c9c61 100644
--- a/app/views/projects/merge_requests/index.html.haml
+++ b/app/views/projects/merge_requests/index.html.haml
@@ -1,6 +1,6 @@
- @no_container = true
- @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
- page_title "Merge Requests"
diff --git a/app/views/projects/notes/_actions.html.haml b/app/views/projects/notes/_actions.html.haml
index 5ea653ccad5..b4fe1cabdfd 100644
--- a/app/views/projects/notes/_actions.html.haml
+++ b/app/views/projects/notes/_actions.html.haml
@@ -36,7 +36,7 @@
%template{ 'v-else' => '' }
= render 'shared/icons/icon_resolve_discussion.svg'
-- if current_user
+- if can?(current_user, :award_emoji, note)
- if note.emoji_awardable?
- user_authored = note.user_authored?(current_user)
.note-actions-item
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index 94331a16abd..e28accd5b43 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -24,7 +24,7 @@
.text-warning.center.prepend-top-20
%p
= 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
diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml
index 3d5f92f9aaa..98b4d6339da 100644
--- a/app/views/projects/tags/_tag.html.haml
+++ b/app/views/projects/tags/_tag.html.haml
@@ -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
= icon("pencil")
- - 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
- = icon("trash-o")
+ - 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
+ = icon("trash-o")
diff --git a/app/views/projects/tags/show.html.haml b/app/views/projects/tags/show.html.haml
index dfe2c37ed8e..7a3469cdd26 100644
--- a/app/views/projects/tags/show.html.haml
+++ b/app/views/projects/tags/show.html.haml
@@ -28,7 +28,7 @@
= icon('history')
.btn-container.controls-item
= 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
= 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
diff --git a/app/views/projects/tree/_tree_header.html.haml b/app/views/projects/tree/_tree_header.html.haml
index 5ef5e9c09a2..8587d3b0c0d 100644
--- a/app/views/projects/tree/_tree_header.html.haml
+++ b/app/views/projects/tree/_tree_header.html.haml
@@ -1,3 +1,6 @@
+- 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-holder
= render 'shared/ref_switcher', destination: 'tree', path: @path, show_create: true
@@ -15,7 +18,7 @@
%li
= 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
%a.btn.add-to-tree{ addtotree_toggle_attributes }
= sprite_icon('plus', size: 16, css_class: 'pull-left')
@@ -35,7 +38,7 @@
%li
= link_to '#modal-create-new-dir', { 'data-target' => '#modal-create-new-dir', 'data-toggle' => 'modal' } do
#{ _('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
- continue_params = { to: project_new_blob_path(@project, @id),
notice: edit_in_new_fork_notice,
@@ -61,23 +64,25 @@
= link_to fork_path, method: :post do
#{ _('New directory') }
- %li.divider
- %li.dropdown-header
- #{ _('This repository') }
- %li
- = link_to new_project_branch_path(@project) do
- #{ _('New branch') }
- %li
- = link_to new_project_tag_path(@project) do
- #{ _('New tag') }
+ - if can?(current_user, :push_code, @project)
+ %li.divider
+ %li.dropdown-header
+ #{ _('This repository') }
+ %li
+ = link_to new_project_branch_path(@project) do
+ #{ _('New branch') }
+ %li
+ = link_to new_project_tag_path(@project) do
+ #{ _('New tag') }
.tree-controls
= link_to s_('Commits|History'), project_commits_path(@project, @id), class: 'btn'
= render 'projects/find_file_link'
- = succeed " " do
- = link_to ide_edit_path(@project, @id, ""), class: 'btn btn-default' do
- = _('Web IDE')
+ - if can_collaborate
+ = succeed " " do
+ = link_to ide_edit_path(@project, @id, ""), class: 'btn btn-default' do
+ = _('Web IDE')
= render 'projects/buttons/download', project: @project, ref: @ref
diff --git a/app/views/shared/_label.html.haml b/app/views/shared/_label.html.haml
index 56403907844..836df57a3a2 100644
--- a/app/views/shared/_label.html.haml
+++ b/app/views/shared/_label.html.haml
@@ -47,20 +47,20 @@
class: 'text-danger'
.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 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
%span.sr-only Edit
= sprite_icon('pencil')
diff --git a/app/views/shared/milestones/_sidebar.html.haml b/app/views/shared/milestones/_sidebar.html.haml
index a942ebc328b..ee134480705 100644
--- a/app/views/shared/milestones/_sidebar.html.haml
+++ b/app/views/shared/milestones/_sidebar.html.haml
@@ -72,7 +72,7 @@
.title.hide-collapsed
Issues
%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
New issue
.value.hide-collapsed.bold
diff --git a/app/views/shared/notes/_note.html.haml b/app/views/shared/notes/_note.html.haml
index bf359774ead..893a7f26ebd 100644
--- a/app/views/shared/notes/_note.html.haml
+++ b/app/views/shared/notes/_note.html.haml
@@ -2,7 +2,7 @@
- return if note.cross_reference_not_visible_for?(current_user)
- 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)
%li.timeline-entry{ id: dom_id(note),
diff --git a/db/fixtures/development/10_merge_requests.rb b/db/fixtures/development/10_merge_requests.rb
index 30244ee4431..bcfdd058a1c 100644
--- a/db/fixtures/development/10_merge_requests.rb
+++ b/db/fixtures/development/10_merge_requests.rb
@@ -4,7 +4,7 @@ Gitlab::Seeder.quiet do
# Limit the number of merge requests per project to avoid long seeds
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.each do |branch_name|
@@ -21,7 +21,11 @@ Gitlab::Seeder.quiet do
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 '.'
end
end
diff --git a/doc/user/project/settings/index.md b/doc/user/project/settings/index.md
index 888dd0e143a..a387c1e443e 100644
--- a/doc/user/project/settings/index.md
+++ b/doc/user/project/settings/index.md
@@ -57,15 +57,20 @@ Here you can run housekeeping, archive, rename, transfer, or remove a project.
NOTE: **Note:**
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. 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.
-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
NOTE: **Note:**
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 3264a26f7d2..d4cc18f622b 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -189,7 +189,7 @@ module API
post ":id/merge_requests" do
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[:force_remove_source_branch] = mr_params.delete(:remove_source_branch)
diff --git a/lib/api/v3/merge_requests.rb b/lib/api/v3/merge_requests.rb
index ce216497996..9b0f70e2bfe 100644
--- a/lib/api/v3/merge_requests.rb
+++ b/lib/api/v3/merge_requests.rb
@@ -93,7 +93,7 @@ module API
post ":id/merge_requests" do
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[:force_remove_source_branch] = mr_params.delete(:remove_source_branch) if mr_params[:remove_source_branch].present?
diff --git a/lib/gitlab/email/handler/create_merge_request_handler.rb b/lib/gitlab/email/handler/create_merge_request_handler.rb
index 3436306e122..2f864f2082b 100644
--- a/lib/gitlab/email/handler/create_merge_request_handler.rb
+++ b/lib/gitlab/email/handler/create_merge_request_handler.rb
@@ -23,7 +23,8 @@ module Gitlab
def execute
raise ProjectNotFound unless project
- validate_permission!(:create_merge_request)
+ validate_permission!(:create_merge_request_in)
+ validate_permission!(:create_merge_request_from)
verify_record!(
record: create_merge_request,
diff --git a/lib/gitlab/user_access.rb b/lib/gitlab/user_access.rb
index 24393f96d96..69952cbb47c 100644
--- a/lib/gitlab/user_access.rb
+++ b/lib/gitlab/user_access.rb
@@ -51,7 +51,7 @@ module Gitlab
return false unless can_access_git?
if protected?(ProtectedBranch, project, ref)
- user.can?(:delete_protected_branch, project)
+ user.can?(:push_to_delete_protected_branch, project)
else
user.can?(:push_code, project)
end
diff --git a/spec/controllers/concerns/checks_collaboration_spec.rb b/spec/controllers/concerns/checks_collaboration_spec.rb
new file mode 100644
index 00000000000..1bd764290ae
--- /dev/null
+++ b/spec/controllers/concerns/checks_collaboration_spec.rb
@@ -0,0 +1,55 @@
+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
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 01b5506b64b..ca86b0bc737 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -938,7 +938,7 @@ describe Projects::IssuesController do
end
describe 'POST create_merge_request' do
- let(:project) { create(:project, :repository) }
+ let(:project) { create(:project, :repository, :public) }
before do
project.add_developer(user)
@@ -955,6 +955,22 @@ describe Projects::IssuesController do
expect(response).to match_response_schema('merge_request')
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
post :create_merge_request, namespace_id: project.namespace.to_param,
project_id: project.to_param,
diff --git a/spec/factories/award_emoji.rb b/spec/factories/award_emoji.rb
index a0abbbce686..d37e2bf511e 100644
--- a/spec/factories/award_emoji.rb
+++ b/spec/factories/award_emoji.rb
@@ -4,6 +4,10 @@ FactoryBot.define do
user
awardable factory: :issue
+ after(:create) do |award, evaluator|
+ award.awardable.project.add_guest(evaluator.user)
+ end
+
trait :upvote
trait :downvote do
name "thumbsdown"
diff --git a/spec/features/admin/admin_broadcast_messages_spec.rb b/spec/features/admin/admin_broadcast_messages_spec.rb
index 9cb351282a0..430a8d22b0f 100644
--- a/spec/features/admin/admin_broadcast_messages_spec.rb
+++ b/spec/features/admin/admin_broadcast_messages_spec.rb
@@ -45,7 +45,7 @@ feature 'Admin Broadcast Messages' do
page.within('.broadcast-message-preview') do
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
diff --git a/spec/features/groups/show_spec.rb b/spec/features/groups/show_spec.rb
index 4ffadbbcd35..3a0424d60f8 100644
--- a/spec/features/groups/show_spec.rb
+++ b/spec/features/groups/show_spec.rb
@@ -98,7 +98,7 @@ feature 'Group show page' do
it 'shows the project info' do
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
diff --git a/spec/features/merge_request/user_awards_emoji_spec.rb b/spec/features/merge_request/user_awards_emoji_spec.rb
index 2f24cfbd9e3..859a4c65562 100644
--- a/spec/features/merge_request/user_awards_emoji_spec.rb
+++ b/spec/features/merge_request/user_awards_emoji_spec.rb
@@ -35,6 +35,14 @@ describe 'Merge request > User awards emoji', :js do
expect(page).to have_selector('.emoji-menu', count: 1)
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
describe 'logged out' do
diff --git a/spec/features/merge_request/user_cherry_picks_spec.rb b/spec/features/merge_request/user_cherry_picks_spec.rb
index 494096b21c0..61d1bdaa95a 100644
--- a/spec/features/merge_request/user_cherry_picks_spec.rb
+++ b/spec/features/merge_request/user_cherry_picks_spec.rb
@@ -40,6 +40,14 @@ describe 'Merge request > User cherry-picks', :js do
expect(page).to have_link 'Cherry-pick'
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
diff --git a/spec/features/projects/awards/user_interacts_with_awards_in_issue_spec.rb b/spec/features/projects/awards/user_interacts_with_awards_in_issue_spec.rb
index adff0a10f0e..12e07647ecd 100644
--- a/spec/features/projects/awards/user_interacts_with_awards_in_issue_spec.rb
+++ b/spec/features/projects/awards/user_interacts_with_awards_in_issue_spec.rb
@@ -99,6 +99,74 @@ describe 'User interacts with awards in an issue', :js do
click_button('Comment')
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
diff --git a/spec/features/projects/branches_spec.rb b/spec/features/projects/branches_spec.rb
index 2a9d9e6416c..b7ce1b9993a 100644
--- a/spec/features/projects/branches_spec.rb
+++ b/spec/features/projects/branches_spec.rb
@@ -195,6 +195,26 @@ describe 'Branches' do
expect(page).to have_content("Protected branches can be managed in project settings")
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
context 'logged out' do
@@ -204,7 +224,7 @@ describe 'Branches' do
it 'does not show merge request button' 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
diff --git a/spec/features/projects/commit/cherry_pick_spec.rb b/spec/features/projects/commit/cherry_pick_spec.rb
index c4c399e3058..1df45865d6f 100644
--- a/spec/features/projects/commit/cherry_pick_spec.rb
+++ b/spec/features/projects/commit/cherry_pick_spec.rb
@@ -89,4 +89,15 @@ describe 'Cherry-pick Commits' do
expect(page).to have_content('The commit has been successfully cherry-picked.')
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
diff --git a/spec/features/projects/commit/user_reverts_commit_spec.rb b/spec/features/projects/commit/user_reverts_commit_spec.rb
index 221f1d7757e..42844a03ea6 100644
--- a/spec/features/projects/commit/user_reverts_commit_spec.rb
+++ b/spec/features/projects/commit/user_reverts_commit_spec.rb
@@ -10,13 +10,16 @@ describe 'User reverts a commit', :js do
sign_in(user)
visit(project_commit_path(project, sample_commit.id))
+ end
+ def click_revert
find('.header-action-buttons .dropdown').click
find('a[href="#modal-revert-commit"]').click
end
context 'without creating a new merge request' do
before do
+ click_revert
page.within('#modal-revert-commit') do
uncheck('create_merge_request')
click_button('Revert')
@@ -44,6 +47,10 @@ describe 'User reverts a commit', :js do
end
context 'with creating a new merge request' do
+ before do
+ click_revert
+ end
+
it 'reverts a commit' do
page.within('#modal-revert-commit') do
click_button('Revert')
@@ -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")
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
diff --git a/spec/features/projects/files/user_edits_files_spec.rb b/spec/features/projects/files/user_edits_files_spec.rb
index 523a9f3f4fe..dc6e4fd27cb 100644
--- a/spec/features/projects/files/user_edits_files_spec.rb
+++ b/spec/features/projects/files/user_edits_files_spec.rb
@@ -12,6 +12,23 @@ describe 'Projects > Files > User edits files' do
sign_in(user)
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
before do
project.add_master(user)
@@ -85,6 +102,8 @@ describe 'Projects > Files > User edits files' do
expect(page).to have_css('.line_holder.new')
end
+
+ it_behaves_like 'unavailable for an archived project'
end
context 'when an user does not have write access' 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("into #{project2.full_path}")
end
+
+ it_behaves_like 'unavailable for an archived project' do
+ let(:project) { project2 }
+ end
end
end
end
diff --git a/spec/features/projects/issues/user_views_issue_spec.rb b/spec/features/projects/issues/user_views_issue_spec.rb
index f7f2cde3d64..4093876c289 100644
--- a/spec/features/projects/issues/user_views_issue_spec.rb
+++ b/spec/features/projects/issues/user_views_issue_spec.rb
@@ -6,11 +6,27 @@ describe "User views issue" do
set(:issue) { create(:issue, project: project, description: "# Description header", author: user) }
before do
- project.add_guest(user)
+ project.add_developer(user)
sign_in(user)
visit(project_issue_path(project, issue))
end
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
diff --git a/spec/features/projects/merge_request_button_spec.rb b/spec/features/projects/merge_request_button_spec.rb
index 40689964b91..b571d5a0e26 100644
--- a/spec/features/projects/merge_request_button_spec.rb
+++ b/spec/features/projects/merge_request_button_spec.rb
@@ -45,6 +45,18 @@ feature 'Merge Request button' do
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
context 'logged in as non-member' do
diff --git a/spec/features/projects/merge_requests/user_reverts_merge_request_spec.rb b/spec/features/projects/merge_requests/user_reverts_merge_request_spec.rb
index a41d683dbbb..f3e97bc9eb2 100644
--- a/spec/features/projects/merge_requests/user_reverts_merge_request_spec.rb
+++ b/spec/features/projects/merge_requests/user_reverts_merge_request_spec.rb
@@ -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.')
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
diff --git a/spec/features/projects/merge_requests/user_views_open_merge_requests_spec.rb b/spec/features/projects/merge_requests/user_views_open_merge_requests_spec.rb
index bf95dbb7d09..115e548b691 100644
--- a/spec/features/projects/merge_requests/user_views_open_merge_requests_spec.rb
+++ b/spec/features/projects/merge_requests/user_views_open_merge_requests_spec.rb
@@ -94,6 +94,18 @@ describe 'User views open merge requests' do
end
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
diff --git a/spec/features/projects/show/user_sees_collaboration_links_spec.rb b/spec/features/projects/show/user_sees_collaboration_links_spec.rb
new file mode 100644
index 00000000000..7b3711531c6
--- /dev/null
+++ b/spec/features/projects/show/user_sees_collaboration_links_spec.rb
@@ -0,0 +1,87 @@
+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
diff --git a/spec/finders/merge_request_target_project_finder_spec.rb b/spec/finders/merge_request_target_project_finder_spec.rb
index c81bfd7932c..f302cf80ce8 100644
--- a/spec/finders/merge_request_target_project_finder_spec.rb
+++ b/spec/finders/merge_request_target_project_finder_spec.rb
@@ -19,6 +19,12 @@ describe MergeRequestTargetProjectFinder do
expect(finder.execute).to contain_exactly(forked_project)
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
context 'public projects' do
diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb
index aeef5352333..8bb2e234e9a 100644
--- a/spec/helpers/issues_helper_spec.rb
+++ b/spec/helpers/issues_helper_spec.rb
@@ -96,13 +96,32 @@ describe IssuesHelper do
describe '#award_state_class' do
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
- 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
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
@@ -144,4 +163,26 @@ describe IssuesHelper do
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
diff --git a/spec/javascripts/notes/components/note_actions_spec.js b/spec/javascripts/notes/components/note_actions_spec.js
index ab81aabb992..1dfe890e05e 100644
--- a/spec/javascripts/notes/components/note_actions_spec.js
+++ b/spec/javascripts/notes/components/note_actions_spec.js
@@ -3,7 +3,7 @@ import store from '~/notes/stores';
import noteActions from '~/notes/components/note_actions.vue';
import { userDataMock } from '../mock_data';
-describe('issse_note_actions component', () => {
+describe('issue_note_actions component', () => {
let vm;
let Component;
@@ -24,6 +24,7 @@ describe('issse_note_actions component', () => {
authorId: 26,
canDelete: true,
canEdit: true,
+ canAwardEmoji: true,
canReportAsAbuse: true,
noteId: 539,
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', () => {
authorId: 26,
canDelete: false,
canEdit: false,
+ canAwardEmoji: false,
canReportAsAbuse: false,
noteId: 539,
reportAbusePath: '/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F7%23note_539&user_id=26',
diff --git a/spec/javascripts/notes/components/note_awards_list_spec.js b/spec/javascripts/notes/components/note_awards_list_spec.js
index 15995ec5a05..1c30d8691b1 100644
--- a/spec/javascripts/notes/components/note_awards_list_spec.js
+++ b/spec/javascripts/notes/components/note_awards_list_spec.js
@@ -29,6 +29,7 @@ describe('note_awards_list component', () => {
awards: awardsMock,
noteAuthorId: 2,
noteId: 545,
+ canAwardEmoji: true,
toggleAwardPath: '/gitlab-org/gitlab-ce/notes/545/toggle_award_emoji',
},
}).$mount();
@@ -43,14 +44,45 @@ describe('note_awards_list component', () => {
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, 'toggleAwardRequest').and.callThrough();
vm.$el.querySelector('.js-awards-block button').click();
expect(vm.handleAward).toHaveBeenCalledWith('flag_tz');
+ expect(vm.toggleAwardRequest).toHaveBeenCalled();
});
it('should be possible to add new emoji', () => {
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();
+ });
+ });
});
diff --git a/spec/javascripts/notes/components/note_body_spec.js b/spec/javascripts/notes/components/note_body_spec.js
index 0ff804f0e55..4e551496ff0 100644
--- a/spec/javascripts/notes/components/note_body_spec.js
+++ b/spec/javascripts/notes/components/note_body_spec.js
@@ -18,6 +18,7 @@ describe('issue_note_body component', () => {
propsData: {
note,
canEdit: true,
+ canAwardEmoji: true,
},
}).$mount();
});
diff --git a/spec/javascripts/notes/mock_data.js b/spec/javascripts/notes/mock_data.js
index 24388fba219..bfe3a65feee 100644
--- a/spec/javascripts/notes/mock_data.js
+++ b/spec/javascripts/notes/mock_data.js
@@ -9,6 +9,7 @@ export const notesDataMock = {
totalNotes: 1,
closePath: '/twitter/flight/issues/9.json?issue%5Bstate_event%5D=close',
reopenPath: '/twitter/flight/issues/9.json?issue%5Bstate_event%5D=reopen',
+ canAwardEmoji: true,
};
export const userDataMock = {
@@ -30,6 +31,7 @@ export const noteableDataMock = {
current_user: {
can_create_note: true,
can_update: true,
+ can_award_emoji: true,
},
description: '',
due_date: null,
@@ -86,7 +88,10 @@ export const individualNote = {
human_access: 'Owner',
note: 'sdfdsaf',
note_html: "<p dir='auto'>sdfdsaf</p>",
- current_user: { can_edit: true },
+ current_user: {
+ can_edit: true,
+ can_award_emoji: true,
+ },
discussion_id: '0fb4e0e3f9276e55ff32eb4195add694aece4edd',
emoji_awardable: true,
award_emoji: [
@@ -129,6 +134,7 @@ export const note = {
note_html: '<p dir="auto">Vel id placeat reprehenderit sit numquam.</p>',
current_user: {
can_edit: true,
+ can_award_emoji: true,
},
discussion_id: 'd3842a451b7f3d9a5dfce329515127b2d29a4cd0',
emoji_awardable: true,
@@ -187,6 +193,7 @@ export const discussionMock = {
note_html: "<p dir='auto'>THIS IS A DICUSSSION!</p>",
current_user: {
can_edit: true,
+ can_award_emoji: true,
},
discussion_id: '9e3bd2f71a01de45fd166e6719eb380ad9f270b1',
emoji_awardable: true,
@@ -231,6 +238,7 @@ export const discussionMock = {
},
current_user: {
can_edit: true,
+ can_award_emoji: true,
},
discussion_id: '9e3bd2f71a01de45fd166e6719eb380ad9f270b1',
emoji_awardable: true,
@@ -275,6 +283,7 @@ export const discussionMock = {
},
current_user: {
can_edit: true,
+ can_award_emoji: true,
},
discussion_id: '9e3bd2f71a01de45fd166e6719eb380ad9f270b1',
emoji_awardable: true,
@@ -365,6 +374,7 @@ export const INDIVIDUAL_NOTE_RESPONSE_MAP = {
note_html: '\u003cp dir="auto"\u003esdfdsaf\u003c/p\u003e',
current_user: {
can_edit: true,
+ can_award_emoji: true,
},
discussion_id: '0fb4e0e3f9276e55ff32eb4195add694aece4edd',
emoji_awardable: true,
@@ -425,6 +435,7 @@ export const INDIVIDUAL_NOTE_RESPONSE_MAP = {
note_html: '\u003cp dir="auto"\u003eNew note!\u003c/p\u003e',
current_user: {
can_edit: true,
+ can_award_emoji: true,
},
discussion_id: '70d5c92a4039a36c70100c6691c18c27e4b0a790',
emoji_awardable: true,
@@ -478,6 +489,7 @@ export const INDIVIDUAL_NOTE_RESPONSE_MAP = {
},
current_user: {
can_edit: true,
+ can_award_emoji: true,
},
discussion_id: 'a3ed36e29b1957efb3b68c53e2d7a2b24b1df052',
emoji_awardable: true,
@@ -527,6 +539,7 @@ export const DISCUSSION_NOTE_RESPONSE_MAP = {
note_html: '\u003cp dir="auto"\u003eAdding a comment\u003c/p\u003e',
current_user: {
can_edit: true,
+ can_award_emoji: true,
},
discussion_id: 'a3ed36e29b1957efb3b68c53e2d7a2b24b1df052',
emoji_awardable: true,
diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb
index cd175dba6da..199f49d0bf2 100644
--- a/spec/models/ability_spec.rb
+++ b/spec/models/ability_spec.rb
@@ -7,62 +7,6 @@ describe Ability do
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
context 'using a public project' do
it 'returns all the users' do
diff --git a/spec/models/concerns/awardable_spec.rb b/spec/models/concerns/awardable_spec.rb
index 34f923d3f0c..a980cff28fb 100644
--- a/spec/models/concerns/awardable_spec.rb
+++ b/spec/models/concerns/awardable_spec.rb
@@ -46,6 +46,31 @@ describe Awardable do
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
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)
diff --git a/spec/policies/note_policy_spec.rb b/spec/policies/note_policy_spec.rb
index 58d36a2c84e..e8096358f7d 100644
--- a/spec/policies/note_policy_spec.rb
+++ b/spec/policies/note_policy_spec.rb
@@ -18,7 +18,6 @@ describe NotePolicy, mdoels: true do
context 'when the project is public' do
context 'when the note author is not a project member' 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(:resolve_note)
expect(policies).to be_allowed(:read_note)
@@ -29,7 +28,6 @@ describe NotePolicy, mdoels: true do
it 'can edit note' do
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(:resolve_note)
expect(policies).to be_allowed(:read_note)
@@ -47,7 +45,6 @@ describe NotePolicy, mdoels: true do
end
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(:resolve_note)
expect(policies).to be_allowed(:read_note)
@@ -56,7 +53,6 @@ describe NotePolicy, mdoels: true do
context 'when the note author is not a project member' 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(:resolve_note)
end
diff --git a/spec/policies/personal_snippet_policy_spec.rb b/spec/policies/personal_snippet_policy_spec.rb
index 50bb0899eba..3809692b373 100644
--- a/spec/policies/personal_snippet_policy_spec.rb
+++ b/spec/policies/personal_snippet_policy_spec.rb
@@ -27,6 +27,7 @@ describe PersonalSnippetPolicy do
it do
is_expected.to be_allowed(:read_personal_snippet)
is_expected.to be_disallowed(:comment_personal_snippet)
+ is_expected.to be_disallowed(:award_emoji)
is_expected.to be_disallowed(*author_permissions)
end
end
@@ -37,6 +38,7 @@ describe PersonalSnippetPolicy do
it do
is_expected.to be_allowed(:read_personal_snippet)
is_expected.to be_allowed(:comment_personal_snippet)
+ is_expected.to be_allowed(:award_emoji)
is_expected.to be_disallowed(*author_permissions)
end
end
@@ -47,6 +49,7 @@ describe PersonalSnippetPolicy do
it do
is_expected.to be_allowed(:read_personal_snippet)
is_expected.to be_allowed(:comment_personal_snippet)
+ is_expected.to be_allowed(:award_emoji)
is_expected.to be_allowed(*author_permissions)
end
end
@@ -61,6 +64,7 @@ describe PersonalSnippetPolicy do
it do
is_expected.to be_disallowed(:read_personal_snippet)
is_expected.to be_disallowed(:comment_personal_snippet)
+ is_expected.to be_disallowed(:award_emoji)
is_expected.to be_disallowed(*author_permissions)
end
end
@@ -71,6 +75,7 @@ describe PersonalSnippetPolicy do
it do
is_expected.to be_allowed(:read_personal_snippet)
is_expected.to be_allowed(:comment_personal_snippet)
+ is_expected.to be_allowed(:award_emoji)
is_expected.to be_disallowed(*author_permissions)
end
end
@@ -81,6 +86,7 @@ describe PersonalSnippetPolicy do
it do
is_expected.to be_disallowed(:read_personal_snippet)
is_expected.to be_disallowed(:comment_personal_snippet)
+ is_expected.to be_disallowed(:award_emoji)
is_expected.to be_disallowed(*author_permissions)
end
end
@@ -91,6 +97,7 @@ describe PersonalSnippetPolicy do
it do
is_expected.to be_allowed(:read_personal_snippet)
is_expected.to be_allowed(:comment_personal_snippet)
+ is_expected.to be_allowed(:award_emoji)
is_expected.to be_allowed(*author_permissions)
end
end
@@ -105,6 +112,7 @@ describe PersonalSnippetPolicy do
it do
is_expected.to be_disallowed(:read_personal_snippet)
is_expected.to be_disallowed(:comment_personal_snippet)
+ is_expected.to be_disallowed(:award_emoji)
is_expected.to be_disallowed(*author_permissions)
end
end
@@ -115,6 +123,7 @@ describe PersonalSnippetPolicy do
it do
is_expected.to be_disallowed(:read_personal_snippet)
is_expected.to be_disallowed(:comment_personal_snippet)
+ is_expected.to be_disallowed(:award_emoji)
is_expected.to be_disallowed(*author_permissions)
end
end
@@ -125,6 +134,7 @@ describe PersonalSnippetPolicy do
it do
is_expected.to be_disallowed(:read_personal_snippet)
is_expected.to be_disallowed(:comment_personal_snippet)
+ is_expected.to be_disallowed(:award_emoji)
is_expected.to be_disallowed(*author_permissions)
end
end
@@ -135,6 +145,7 @@ describe PersonalSnippetPolicy do
it do
is_expected.to be_allowed(:read_personal_snippet)
is_expected.to be_allowed(:comment_personal_snippet)
+ is_expected.to be_allowed(:award_emoji)
is_expected.to be_allowed(*author_permissions)
end
end
diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb
index 905d82b3bb1..8b9c4ac0b4b 100644
--- a/spec/policies/project_policy_spec.rb
+++ b/spec/policies/project_policy_spec.rb
@@ -14,7 +14,8 @@ describe ProjectPolicy do
read_project read_board read_list read_wiki read_issue
read_project_for_iids read_issue_iid read_merge_request_iid read_label
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
@@ -35,7 +36,7 @@ describe ProjectPolicy do
%i[
admin_milestone admin_merge_request update_merge_request create_commit_status
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
create_environment create_deployment
]
@@ -43,7 +44,7 @@ describe ProjectPolicy do
let(:base_master_permissions) do
%i[
- delete_protected_branch update_project_snippet update_environment
+ push_to_delete_protected_branch update_project_snippet update_environment
update_deployment admin_project_snippet
admin_project_member admin_note admin_wiki admin_project
admin_commit_status admin_build admin_container_image
@@ -136,13 +137,66 @@ describe ProjectPolicy do
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
context 'abilities for public projects' do
context 'when a project has pending invites' do
let(:group) { create(:group, :public) }
let(:project) { create(:project, :public, namespace: group) }
- let(:user_permissions) { [:create_project, :create_issue, :create_note, :upload_file] }
- let(:anonymous_permissions) { guest_permissions - user_permissions }
+ let(:user_permissions) { [:create_merge_request_in, :create_project, :create_issue, :create_note, :upload_file, :award_emoji] }
+ let(:anonymous_permissions) { guest_permissions - user_permissions }
subject { described_class.new(nil, project) }
@@ -154,6 +208,10 @@ describe ProjectPolicy do
expect_allowed(*anonymous_permissions)
expect_disallowed(*user_permissions)
end
+
+ it_behaves_like 'archived project policies' do
+ let(:regular_abilities) { anonymous_permissions }
+ end
end
end
@@ -184,6 +242,10 @@ describe ProjectPolicy do
expect_disallowed(*owner_permissions)
end
+ it_behaves_like 'archived project policies' do
+ let(:regular_abilities) { guest_permissions }
+ end
+
context 'public builds enabled' do
it do
expect_allowed(*guest_permissions)
@@ -224,12 +286,15 @@ describe ProjectPolicy do
it do
expect_allowed(*guest_permissions)
expect_allowed(*reporter_permissions)
- expect_allowed(*reporter_permissions)
expect_allowed(*team_member_reporter_permissions)
expect_disallowed(*developer_permissions)
expect_disallowed(*master_permissions)
expect_disallowed(*owner_permissions)
end
+
+ it_behaves_like 'archived project policies' do
+ let(:regular_abilities) { reporter_permissions }
+ end
end
end
@@ -247,6 +312,10 @@ describe ProjectPolicy do
expect_disallowed(*master_permissions)
expect_disallowed(*owner_permissions)
end
+
+ it_behaves_like 'archived project policies' do
+ let(:regular_abilities) { developer_permissions }
+ end
end
end
@@ -264,6 +333,10 @@ describe ProjectPolicy do
expect_allowed(*master_permissions)
expect_disallowed(*owner_permissions)
end
+
+ it_behaves_like 'archived project policies' do
+ let(:regular_abilities) { master_permissions }
+ end
end
end
@@ -281,6 +354,10 @@ describe ProjectPolicy do
expect_allowed(*master_permissions)
expect_allowed(*owner_permissions)
end
+
+ it_behaves_like 'archived project policies' do
+ let(:regular_abilities) { owner_permissions }
+ end
end
end
@@ -298,6 +375,10 @@ describe ProjectPolicy do
expect_allowed(*master_permissions)
expect_allowed(*owner_permissions)
end
+
+ it_behaves_like 'archived project policies' do
+ let(:regular_abilities) { owner_permissions }
+ end
end
end
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 3764aec0c71..f64623d7018 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -861,7 +861,7 @@ describe API::MergeRequests do
expect(json_response['title']).to eq('Test merge_request')
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)
post api("/projects/#{forked_project.id}/merge_requests", user2),
@@ -871,7 +871,7 @@ describe API::MergeRequests do
author: user2,
target_project_id: project.id
- expect(response).to have_gitlab_http_status(422)
+ expect(response).to have_gitlab_http_status(403)
end
it "returns 400 when source_branch is missing" do
diff --git a/spec/requests/api/v3/merge_requests_spec.rb b/spec/requests/api/v3/merge_requests_spec.rb
index 6b748369f0d..be70cb24dce 100644
--- a/spec/requests/api/v3/merge_requests_spec.rb
+++ b/spec/requests/api/v3/merge_requests_spec.rb
@@ -340,7 +340,7 @@ describe API::MergeRequests do
expect(json_response['title']).to eq('Test merge_request')
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)
post v3_api("/projects/#{forked_project.id}/merge_requests", user2),
@@ -350,7 +350,7 @@ describe API::MergeRequests do
author: user2,
target_project_id: project.id
- expect(response).to have_gitlab_http_status(422)
+ expect(response).to have_gitlab_http_status(403)
end
it "returns 400 when source_branch is missing" do
diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb
index 44a83c436cb..736a50b2c15 100644
--- a/spec/services/merge_requests/create_service_spec.rb
+++ b/spec/services/merge_requests/create_service_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe MergeRequests::CreateService do
+ include ProjectForksHelper
+
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
let(:assignee) { create(:user) }
@@ -300,7 +302,7 @@ describe MergeRequests::CreateService do
end
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
{
@@ -334,6 +336,26 @@ describe MergeRequests::CreateService do
.to raise_error Gitlab::Access::AccessDeniedError
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
context 'when user sets source project id' do
diff --git a/spec/support/matchers/have_emoji.rb b/spec/support/matchers/have_emoji.rb
new file mode 100644
index 00000000000..23fb8e9c1c4
--- /dev/null
+++ b/spec/support/matchers/have_emoji.rb
@@ -0,0 +1,5 @@
+RSpec::Matchers.define :have_emoji do |emoji_name|
+ match do |actual|
+ expect(actual).to have_selector("gl-emoji[data-name='#{emoji_name}']")
+ end
+end
diff --git a/spec/views/projects/buttons/_dropdown.html.haml_spec.rb b/spec/views/projects/buttons/_dropdown.html.haml_spec.rb
index d0e692635b9..8b9aab30286 100644
--- a/spec/views/projects/buttons/_dropdown.html.haml_spec.rb
+++ b/spec/views/projects/buttons/_dropdown.html.haml_spec.rb
@@ -8,7 +8,8 @@ describe 'projects/buttons/_dropdown' do
assign(:project, project)
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
context 'empty repository' do
diff --git a/spec/views/projects/commit/_commit_box.html.haml_spec.rb b/spec/views/projects/commit/_commit_box.html.haml_spec.rb
index 448b925cf34..2fdd28a3be4 100644
--- a/spec/views/projects/commit/_commit_box.html.haml_spec.rb
+++ b/spec/views/projects/commit/_commit_box.html.haml_spec.rb
@@ -7,6 +7,7 @@ describe 'projects/commit/_commit_box.html.haml' do
before do
assign(:project, project)
assign(:commit, project.commit)
+ allow(view).to receive(:current_user).and_return(user)
allow(view).to receive(:can_collaborate_with_project?).and_return(false)
end
@@ -47,7 +48,8 @@ describe 'projects/commit/_commit_box.html.haml' do
context 'viewing a commit' do
context 'as a developer' 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
it 'has a link to create a new tag' do
@@ -58,10 +60,6 @@ describe 'projects/commit/_commit_box.html.haml' do
end
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
render