From 9b973ec576e80dbb2c7524fc181d3a61c91f6cff Mon Sep 17 00:00:00 2001 From: Alex Buijs Date: Tue, 6 Dec 2022 14:48:09 +0100 Subject: [PATCH 1/2] Move hidden logic from Issue to Issuable In order to make it reusable for MergeRequest --- app/finders/issuable_finder/params.rb | 5 ++ app/finders/issues_finder.rb | 2 +- app/finders/issues_finder/params.rb | 8 +-- app/helpers/issuables_helper.rb | 16 ++++- app/helpers/issues_helper.rb | 12 ---- app/models/concerns/issuable.rb | 12 ++++ app/models/issue.rb | 12 ---- app/policies/issuable_policy.rb | 3 + app/policies/issue_policy.rb | 3 - app/views/projects/issues/_issue.html.haml | 2 +- .../issue_type/_details_header.html.haml | 2 +- locale/gitlab.pot | 3 + spec/helpers/issuables_helper_spec.rb | 62 +++++++++++++++++++ spec/helpers/issues_helper_spec.rb | 51 --------------- spec/models/concerns/issuable_spec.rb | 47 ++++++++++++++ spec/models/issue_spec.rb | 20 ------ 16 files changed, 151 insertions(+), 109 deletions(-) diff --git a/app/finders/issuable_finder/params.rb b/app/finders/issuable_finder/params.rb index 32d50802537cee..4e17f06e1c16a3 100644 --- a/app/finders/issuable_finder/params.rb +++ b/app/finders/issuable_finder/params.rb @@ -195,6 +195,11 @@ def parent project || group end + def user_can_see_all_issuables? + Ability.allowed?(current_user, :read_all_resources) + end + strong_memoize_attr :user_can_see_all_issuables?, :user_can_see_all_issuables + private def projects_public_or_visible_to_user diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb index e12dce744b5748..bd81f06f93b156 100644 --- a/app/finders/issues_finder.rb +++ b/app/finders/issues_finder.rb @@ -49,7 +49,7 @@ def params_class # rubocop: disable CodeReuse/ActiveRecord def with_confidentiality_access_check - return model_class.all if params.user_can_see_all_issues? + return model_class.all if params.user_can_see_all_issuables? # Only admins can see hidden issues, so for non-admins, we filter out any hidden issues issues = model_class.without_hidden diff --git a/app/finders/issues_finder/params.rb b/app/finders/issues_finder/params.rb index 7f8acb79ed688c..786bfbd4113b84 100644 --- a/app/finders/issues_finder/params.rb +++ b/app/finders/issues_finder/params.rb @@ -44,7 +44,7 @@ def user_can_see_all_confidential_issues? if parent Ability.allowed?(current_user, :read_confidential_issues, parent) else - user_can_see_all_issues? + user_can_see_all_issuables? end end end @@ -54,12 +54,6 @@ def user_cannot_see_confidential_issues? current_user.blank? end - - def user_can_see_all_issues? - strong_memoize(:user_can_see_all_issues) do - Ability.allowed?(current_user, :read_all_resources) - end - end end end diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb index 2b21d8c51e6921..7d99b0da89003b 100644 --- a/app/helpers/issuables_helper.rb +++ b/app/helpers/issuables_helper.rb @@ -275,7 +275,7 @@ def issue_only_initial_data(issuable) zoomMeetingUrl: ZoomMeeting.canonical_meeting_url(issuable), sentryIssueIdentifier: SentryIssue.find_by(issue: issuable)&.sentry_issue_identifier, # rubocop:disable CodeReuse/ActiveRecord iid: issuable.iid.to_s, - isHidden: issue_hidden?(issuable), + isHidden: issuable_hidden?(issuable), canCreateIncident: create_issue_type_allowed?(issuable.project, :incident) } end @@ -372,6 +372,20 @@ def state_name_with_icon(issuable) end end + def issuable_hidden?(issuable) + Feature.enabled?(:ban_user_feature_flag) && issuable.hidden? + end + + def hidden_issuable_icon(issuable) + return unless issuable_hidden?(issuable) + + title = format(_('This %{issuable} is hidden because its author has been banned'), + issuable: issuable.human_class_name) + content_tag(:span, class: 'has-tooltip', title: title) do + sprite_icon('spam', css_class: 'gl-vertical-align-text-bottom') + end + end + private def sidebar_gutter_collapsed? diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 1d68dccc741d9b..101df8cdd41f0c 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -70,18 +70,6 @@ def confidential_icon(issue) sprite_icon('eye-slash', css_class: 'gl-vertical-align-text-bottom') if issue.confidential? end - def issue_hidden?(issue) - Feature.enabled?(:ban_user_feature_flag) && issue.hidden? - end - - def hidden_issue_icon(issue) - return unless issue_hidden?(issue) - - content_tag(:span, class: 'has-tooltip', title: _('This issue is hidden because its author has been banned')) do - sprite_icon('spam', css_class: 'gl-vertical-align-text-bottom') - end - end - def award_user_list(awards, current_user, limit: 10) names = awards.map do |award| award.user == current_user ? 'You' : award.user.name diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 9f0cd96a8f899a..1a50ebde0a39e5 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -144,6 +144,14 @@ def system_note_metadata_loaded? includes(*associations) end + scope :without_hidden, -> { + if Feature.enabled?(:ban_user_feature_flag) + where.not(author_id: Users::BannedUser.all.select(:user_id)) + else + all + end + } + attr_mentionable :title, pipeline: :single_line attr_mentionable :description @@ -227,6 +235,10 @@ def severity issuable_severity&.severity || IssuableSeverity::DEFAULT end + def hidden? + author&.banned? + end + private def description_max_length_for_new_records_is_valid diff --git a/app/models/issue.rb b/app/models/issue.rb index b338ecfce88e7f..f517f42d6ba27d 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -178,14 +178,6 @@ def most_recent scope :confidential_only, -> { where(confidential: true) } - scope :without_hidden, -> { - if Feature.enabled?(:ban_user_feature_flag) - where.not(author_id: Users::BannedUser.all.select(:user_id)) - else - all - end - } - scope :counts_by_state, -> { reorder(nil).group(:state_id).count } scope :service_desk, -> { where(author: ::User.support_bot) } @@ -658,10 +650,6 @@ def readable_by?(user) end end - def hidden? - author&.banned? - end - # Necessary until all issues are backfilled and we add a NOT NULL constraint on the DB def work_item_type super || WorkItems::Type.default_by_type(issue_type) diff --git a/app/policies/issuable_policy.rb b/app/policies/issuable_policy.rb index aa07bb7dc5fefb..779384ee3fe9c0 100644 --- a/app/policies/issuable_policy.rb +++ b/app/policies/issuable_policy.rb @@ -16,6 +16,9 @@ class IssuablePolicy < BasePolicy condition(:is_incident) { @subject.incident? } + desc "Issuable is hidden" + condition(:hidden, scope: :subject) { @subject.hidden? } + rule { can?(:guest_access) & assignee_or_author & ~is_incident }.policy do enable :read_issue enable :update_issue diff --git a/app/policies/issue_policy.rb b/app/policies/issue_policy.rb index 491eebe9dafa07..7d4e42580b8601 100644 --- a/app/policies/issue_policy.rb +++ b/app/policies/issue_policy.rb @@ -21,9 +21,6 @@ class IssuePolicy < IssuablePolicy desc "Issue is confidential" condition(:confidential, scope: :subject) { @subject.confidential? } - desc "Issue is hidden" - condition(:hidden, scope: :subject) { @subject.hidden? } - desc "Issue is persisted" condition(:persisted, scope: :subject) { @subject.persisted? } diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index 1d3320e4f82280..a34ed332fcf697 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -6,7 +6,7 @@ - if issue.confidential? %span.has-tooltip{ title: _('Confidential') } = confidential_icon(issue) - = hidden_issue_icon(issue) + = hidden_issuable_icon(issue) = link_to issue.title, issue_path(issue), class: 'js-prefetch-document' = render_if_exists 'projects/issues/subepic_flag', issue: issue - if issue.tasks? diff --git a/app/views/shared/issue_type/_details_header.html.haml b/app/views/shared/issue_type/_details_header.html.haml index ccb501dae11329..c8762f8e060edd 100644 --- a/app/views/shared/issue_type/_details_header.html.haml +++ b/app/views/shared/issue_type/_details_header.html.haml @@ -13,7 +13,7 @@ %span.gl-display-none.gl-sm-display-block.gl-ml-2 = _('Open') - #js-issuable-header-warnings{ data: { hidden: issue_hidden?(issuable).to_s } } + #js-issuable-header-warnings{ data: { hidden: issuable_hidden?(issuable).to_s } } = issuable_meta(issuable, @project) %a.btn.gl-button.btn-default.btn-icon.float-right.gl-display-block.d-sm-none.gutter-toggle.issuable-gutter-toggle.js-sidebar-toggle{ href: "#" } diff --git a/locale/gitlab.pot b/locale/gitlab.pot index f0b142047aa477..992ea77766c1ea 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -42067,6 +42067,9 @@ msgstr "" msgid "This %{issuableDisplayName} is locked. Only project members can comment." msgstr "" +msgid "This %{issuable} is hidden because its author has been banned" +msgstr "" + msgid "This %{issuable} is locked. Only %{strong_open}project members%{strong_close} can comment." msgstr "" diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb index 15b57a4c9eb034..2a61b38337bd8e 100644 --- a/spec/helpers/issuables_helper_spec.rb +++ b/spec/helpers/issuables_helper_spec.rb @@ -629,4 +629,66 @@ expect(helper.sidebar_milestone_tooltip_label(milestone)).to eq('<img onerror=alert(1)>
Milestone') end end + + describe '#issuable_hidden?' do + let_it_be(:issuable) { build(:issue) } + + context 'when issuable is hidden' do + let_it_be(:banned_user) { build(:user, :banned) } + let_it_be(:hidden_issuable) { build(:issue, author: banned_user) } + + context 'when `ban_user_feature_flag` feature flag is enabled' do + it 'returns `true`' do + expect(helper.issuable_hidden?(hidden_issuable)).to eq(true) + end + end + + context 'when `ban_user_feature_flag` feature flag is disabled' do + before do + stub_feature_flags(ban_user_feature_flag: false) + end + + it 'returns `false`' do + expect(helper.issuable_hidden?(hidden_issuable)).to eq(false) + end + end + end + + context 'when issuable is not hidden' do + it 'returns `false`' do + expect(helper.issuable_hidden?(issuable)).to eq(false) + end + end + end + + describe '#hidden_issuable_icon' do + let_it_be(:banned_user) { build(:user, :banned) } + let_it_be(:hidden_issuable) { build(:issue, author: banned_user) } + let_it_be(:issuable) { build(:issue) } + let_it_be(:mock_svg) { ''.html_safe } + + before do + allow(helper).to receive(:sprite_icon).and_return(mock_svg) + end + + context 'when issuable is hidden' do + it 'returns icon with tooltip' do + expect(helper.hidden_issuable_icon(hidden_issuable)).to eq("") + end + + context 'when issuable is a merge request' do + let_it_be(:hidden_issuable) { build(:merge_request, author: banned_user) } + + it 'returns icon with tooltip' do + expect(helper.hidden_issuable_icon(hidden_issuable)).to eq("") + end + end + end + + context 'when issuable is not hidden' do + it 'returns `nil`' do + expect(helper.hidden_issuable_icon(issuable)).to be_nil + end + end + end end diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb index ed363268cdfcdd..39e50070169666 100644 --- a/spec/helpers/issues_helper_spec.rb +++ b/spec/helpers/issues_helper_spec.rb @@ -508,55 +508,4 @@ end end end - - describe '#issue_hidden?' do - context 'when issue is hidden' do - let_it_be(:banned_user) { build(:user, :banned) } - let_it_be(:hidden_issue) { build(:issue, author: banned_user) } - - context 'when `ban_user_feature_flag` feature flag is enabled' do - it 'returns `true`' do - expect(helper.issue_hidden?(hidden_issue)).to eq(true) - end - end - - context 'when `ban_user_feature_flag` feature flag is disabled' do - before do - stub_feature_flags(ban_user_feature_flag: false) - end - - it 'returns `false`' do - expect(helper.issue_hidden?(hidden_issue)).to eq(false) - end - end - end - - context 'when issue is not hidden' do - it 'returns `false`' do - expect(helper.issue_hidden?(issue)).to eq(false) - end - end - end - - describe '#hidden_issue_icon' do - let_it_be(:banned_user) { build(:user, :banned) } - let_it_be(:hidden_issue) { build(:issue, author: banned_user) } - let_it_be(:mock_svg) { ''.html_safe } - - before do - allow(helper).to receive(:sprite_icon).and_return(mock_svg) - end - - context 'when issue is hidden' do - it 'returns icon with tooltip' do - expect(helper.hidden_issue_icon(hidden_issue)).to eq("") - end - end - - context 'when issue is not hidden' do - it 'returns `nil`' do - expect(helper.hidden_issue_icon(issue)).to be_nil - end - end - end end diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index e553e34ab51192..c807bd83984925 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -337,6 +337,53 @@ it { expect(MergeRequest.to_ability_name).to eq("merge_request") } end + describe '.without_hidden' do + let_it_be(:banned_user) { create(:user, :banned) } + + where(issuable_type: [:issue, :merge_request]) + + with_them do + let!(:public_issuable) { create(issuable_type) } + let!(:hidden_issuable) { create(issuable_type, author: banned_user) } + + subject { issuable_type.to_s.classify.constantize.without_hidden } + + it 'only returns public issuables' do + expect(subject).to match_array([public_issuable]) + end + + context 'when feature flag is disabled' do + before do + stub_feature_flags(ban_user_feature_flag: false) + end + + it 'returns public and hidden issuables' do + expect(subject).to contain_exactly(public_issuable, hidden_issuable) + end + end + end + end + + describe '#hidden?' do + let_it_be(:author) { create(:user) } + + where(issuable_type: [:issue, :merge_request]) + + with_them do + let(:issuable) { build_stubbed(issuable_type, author: author) } + + subject { issuable.hidden? } + + it { is_expected.to eq(false) } + + context 'when the author is banned' do + let_it_be(:author) { create(:user, :banned) } + + it { is_expected.to eq(true) } + end + end + end + describe "#sort_by_attribute" do let(:project) { create(:project) } diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 82ee062aef037f..c8904519629147 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -1432,26 +1432,6 @@ end end - describe '.without_hidden' do - let_it_be(:banned_user) { create(:user, :banned) } - let_it_be(:public_issue) { create(:issue, project: reusable_project) } - let_it_be(:hidden_issue) { create(:issue, project: reusable_project, author: banned_user) } - - it 'only returns without_hidden issues' do - expect(described_class.without_hidden).to eq([public_issue]) - end - - context 'when feature flag is disabled' do - before do - stub_feature_flags(ban_user_feature_flag: false) - end - - it 'returns public and hidden issues' do - expect(described_class.without_hidden).to contain_exactly(public_issue, hidden_issue) - end - end - end - describe '.by_project_id_and_iid' do let_it_be(:issue_a) { create(:issue, project: reusable_project) } let_it_be(:issue_b) { create(:issue, iid: issue_a.iid) } -- GitLab From cfc6d35343edea6d71b3d6287e327d3fd39fd2a1 Mon Sep 17 00:00:00 2001 From: Alex Buijs Date: Mon, 5 Dec 2022 15:53:51 +0100 Subject: [PATCH 2/2] Hide merge requests from banned users And show a hidden icon for admins and auditors --- .../admin/users/components/actions/ban.vue | 4 ++- .../components/issuable_header_warnings.vue | 6 +++-- .../merge_requests/application_controller.rb | 4 +++ app/finders/issuable_finder.rb | 5 +++- .../concerns/resolves_merge_requests.rb | 2 +- app/policies/merge_request_policy.rb | 4 +++ .../merge_requests/_merge_request.html.haml | 1 + .../merge_requests/_mr_title.html.haml | 2 +- doc/user/admin_area/moderate_users.md | 2 +- ee/spec/finders/merge_requests_finder_spec.rb | 17 ++++++++++++ locale/gitlab.pot | 2 +- .../admin_views_hidden_merge_request_spec.rb | 26 +++++++++++++++++++ .../admin_views_hidden_merge_requests_spec.rb | 26 +++++++++++++++++++ .../issuable_header_warnings_spec.js | 3 ++- spec/models/concerns/issuable_spec.rb | 6 ++--- spec/policies/merge_request_policy_spec.rb | 16 ++++++++++++ .../merge_requests_controller_spec.rb | 26 +++++++++++++++---- 17 files changed, 135 insertions(+), 17 deletions(-) create mode 100644 spec/features/merge_request/admin_views_hidden_merge_request_spec.rb create mode 100644 spec/features/merge_requests/admin_views_hidden_merge_requests_spec.rb diff --git a/app/assets/javascripts/admin/users/components/actions/ban.vue b/app/assets/javascripts/admin/users/components/actions/ban.vue index 55938832dce5d4..898a688c203f07 100644 --- a/app/assets/javascripts/admin/users/components/actions/ban.vue +++ b/app/assets/javascripts/admin/users/components/actions/ban.vue @@ -11,7 +11,9 @@ const messageHtml = `

${s__('AdminUsers|You can unban their account in the future. Their data remains intact.')}

${sprintf( diff --git a/app/assets/javascripts/issuable/components/issuable_header_warnings.vue b/app/assets/javascripts/issuable/components/issuable_header_warnings.vue index 543dca0afe1f91..a84187ab86b8b4 100644 --- a/app/assets/javascripts/issuable/components/issuable_header_warnings.vue +++ b/app/assets/javascripts/issuable/components/issuable_header_warnings.vue @@ -1,7 +1,7 @@