Commit f0e4e89a authored by Mayra Cabrera's avatar Mayra Cabrera

Merge branch 'issue-hidden-attribute' into 'master'

Hidden issues

See merge request gitlab-org/gitlab!66687
parents 2f6babcb ea86eb57
...@@ -6,10 +6,11 @@ import { I18N_USER_ACTIONS } from '../../constants'; ...@@ -6,10 +6,11 @@ import { I18N_USER_ACTIONS } from '../../constants';
// TODO: To be replaced with <template> content in https://gitlab.com/gitlab-org/gitlab/-/issues/320922 // TODO: To be replaced with <template> content in https://gitlab.com/gitlab-org/gitlab/-/issues/320922
const messageHtml = ` const messageHtml = `
<p>${s__('AdminUsers|When banned, users:')}</p> <p>${s__('AdminUsers|When banned:')}</p>
<ul> <ul>
<li>${s__("AdminUsers|Can't log in.")}</li> <li>${s__("AdminUsers|The user can't log in.")}</li>
<li>${s__("AdminUsers|Can't access Git repositories.")}</li> <li>${s__("AdminUsers|The user can't access git repositories.")}</li>
<li>${s__('AdminUsers|Issues authored by this user are hidden from other users.')}</li>
</ul> </ul>
<p>${s__('AdminUsers|You can unban their account in the future. Their data remains intact.')}</p> <p>${s__('AdminUsers|You can unban their account in the future. Their data remains intact.')}</p>
<p>${sprintf( <p>${sprintf(
......
...@@ -47,17 +47,22 @@ class IssuesFinder < IssuableFinder ...@@ -47,17 +47,22 @@ class IssuesFinder < IssuableFinder
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def with_confidentiality_access_check def with_confidentiality_access_check
return Issue.all if params.user_can_see_all_confidential_issues? return Issue.all if params.user_can_see_all_issues?
# Only admins can see hidden issues, so for non-admins, we filter out any hidden issues
issues = Issue.without_hidden
return issues.all if params.user_can_see_all_confidential_issues?
# If already filtering by assignee we can skip confidentiality since a user # If already filtering by assignee we can skip confidentiality since a user
# can always see confidential issues assigned to them. This is just an # can always see confidential issues assigned to them. This is just an
# optimization since a very common usecase of this Finder is to load the # optimization since a very common usecase of this Finder is to load the
# count of issues assigned to the user for the header bar. # count of issues assigned to the user for the header bar.
return Issue.all if current_user && assignee_filter.includes_user?(current_user) return issues.all if current_user && assignee_filter.includes_user?(current_user)
return Issue.where('issues.confidential IS NOT TRUE') if params.user_cannot_see_confidential_issues? return issues.where('issues.confidential IS NOT TRUE') if params.user_cannot_see_confidential_issues?
Issue.where(' issues.where('
issues.confidential IS NOT TRUE issues.confidential IS NOT TRUE
OR (issues.confidential = TRUE OR (issues.confidential = TRUE
AND (issues.author_id = :user_id AND (issues.author_id = :user_id
......
...@@ -32,7 +32,7 @@ class IssuesFinder ...@@ -32,7 +32,7 @@ class IssuesFinder
if parent if parent
Ability.allowed?(current_user, :read_confidential_issues, parent) Ability.allowed?(current_user, :read_confidential_issues, parent)
else else
Ability.allowed?(current_user, :read_all_resources) user_can_see_all_issues?
end end
end end
end end
...@@ -42,6 +42,12 @@ class IssuesFinder ...@@ -42,6 +42,12 @@ class IssuesFinder
current_user.blank? current_user.blank?
end 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
end end
......
...@@ -37,7 +37,8 @@ module Resolvers ...@@ -37,7 +37,8 @@ module Resolvers
[ [
{ {
project: [:project_feature] project: [:project_feature]
} },
:author
] ]
end end
......
...@@ -52,6 +52,10 @@ module IssuesHelper ...@@ -52,6 +52,10 @@ module IssuesHelper
sprite_icon('eye-slash', css_class: 'gl-vertical-align-text-bottom') if issue.confidential? sprite_icon('eye-slash', css_class: 'gl-vertical-align-text-bottom') if issue.confidential?
end end
def hidden_issue_icon(issue)
sprite_icon('spam', css_class: 'gl-vertical-align-text-bottom') if issue.hidden?
end
def award_user_list(awards, current_user, limit: 10) def award_user_list(awards, current_user, limit: 10)
names = awards.map do |award| names = awards.map do |award|
award.user == current_user ? 'You' : award.user.name award.user == current_user ? 'You' : award.user.name
......
...@@ -130,6 +130,15 @@ class Issue < ApplicationRecord ...@@ -130,6 +130,15 @@ class Issue < ApplicationRecord
scope :public_only, -> { where(confidential: false) } scope :public_only, -> { where(confidential: false) }
scope :confidential_only, -> { where(confidential: true) } scope :confidential_only, -> { where(confidential: true) }
scope :without_hidden, -> {
if Feature.enabled?(:ban_user_feature_flag)
where(id: joins('LEFT JOIN banned_users ON banned_users.user_id = issues.author_id WHERE banned_users.user_id IS NULL')
.select('issues.id'))
else
all
end
}
scope :counts_by_state, -> { reorder(nil).group(:state_id).count } scope :counts_by_state, -> { reorder(nil).group(:state_id).count }
scope :service_desk, -> { where(author: ::User.support_bot) } scope :service_desk, -> { where(author: ::User.support_bot) }
...@@ -551,6 +560,8 @@ class Issue < ApplicationRecord ...@@ -551,6 +560,8 @@ class Issue < ApplicationRecord
true true
elsif confidential? && !assignee_or_author?(user) elsif confidential? && !assignee_or_author?(user)
project.team.member?(user, Gitlab::Access::REPORTER) project.team.member?(user, Gitlab::Access::REPORTER)
elsif hidden?
false
else else
project.public? || project.public? ||
project.internal? && !user.external? || project.internal? && !user.external? ||
...@@ -558,6 +569,10 @@ class Issue < ApplicationRecord ...@@ -558,6 +569,10 @@ class Issue < ApplicationRecord
end end
end end
def hidden?
author&.banned?
end
private private
def spammable_attribute_changed? def spammable_attribute_changed?
...@@ -585,7 +600,7 @@ class Issue < ApplicationRecord ...@@ -585,7 +600,7 @@ class Issue < ApplicationRecord
# Returns `true` if this Issue is visible to everybody. # Returns `true` if this Issue is visible to everybody.
def publicly_visible? def publicly_visible?
project.public? && !confidential? && !::Gitlab::ExternalAuthorization.enabled? project.public? && !confidential? && !hidden? && !::Gitlab::ExternalAuthorization.enabled?
end end
def expire_etag_cache def expire_etag_cache
......
...@@ -15,6 +15,9 @@ class IssuePolicy < IssuablePolicy ...@@ -15,6 +15,9 @@ class IssuePolicy < IssuablePolicy
desc "Issue is confidential" desc "Issue is confidential"
condition(:confidential, scope: :subject) { @subject.confidential? } condition(:confidential, scope: :subject) { @subject.confidential? }
desc "Issue is hidden"
condition(:hidden, scope: :subject) { @subject.hidden? }
desc "Issue is persisted" desc "Issue is persisted"
condition(:persisted, scope: :subject) { @subject.persisted? } condition(:persisted, scope: :subject) { @subject.persisted? }
...@@ -23,6 +26,10 @@ class IssuePolicy < IssuablePolicy ...@@ -23,6 +26,10 @@ class IssuePolicy < IssuablePolicy
prevent :read_issue_iid prevent :read_issue_iid
end end
rule { hidden & ~admin }.policy do
prevent :read_issue
end
rule { ~can?(:read_issue) }.prevent :create_note rule { ~can?(:read_issue) }.prevent :create_note
rule { locked }.policy do rule { locked }.policy do
......
...@@ -12,6 +12,9 @@ ...@@ -12,6 +12,9 @@
- if issue.confidential? - if issue.confidential?
%span.has-tooltip{ title: _('Confidential') } %span.has-tooltip{ title: _('Confidential') }
= confidential_icon(issue) = confidential_icon(issue)
- if Feature.enabled?(:ban_user_feature_flag) && issue.hidden?
%span.has-tooltip{ title: _('This issue is hidden because its author has been banned') }
= hidden_issue_icon(issue)
= link_to issue.title, issue_path(issue) = link_to issue.title, issue_path(issue)
= render_if_exists 'projects/issues/subepic_flag', issue: issue = render_if_exists 'projects/issues/subepic_flag', issue: issue
- if issue.tasks? - if issue.tasks?
......
...@@ -195,12 +195,12 @@ Users can also be activated using the [GitLab API](../../api/users.md#activate-u ...@@ -195,12 +195,12 @@ Users can also be activated using the [GitLab API](../../api/users.md#activate-u
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/327353) in GitLab 14.2. > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/327353) in GitLab 14.2.
GitLab administrators can ban and unban users. The banned user's issues are still displayed. Hiding GitLab administrators can ban and unban users. Banned users are blocked, and their issues are hidden.
a banned user's issues is a [work in progress](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/66687). The banned user's comments are still displayed. Hiding a banned user's comments is [tracked in this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/327356).
### Ban a user ### Ban a user
To completely block a user, administrators can choose to ban the user. To block a user and hide their contributions, administrators can ban the user.
Users can be banned using the Admin Area. To do this: Users can be banned using the Admin Area. To do this:
......
...@@ -237,9 +237,11 @@ RSpec.describe IssuesFinder do ...@@ -237,9 +237,11 @@ RSpec.describe IssuesFinder do
let_it_be(:guest) { create(:user) } let_it_be(:guest) { create(:user) }
let_it_be(:authorized_user) { create(:user) } let_it_be(:authorized_user) { create(:user) }
let_it_be(:banned_user) { create(:user, :banned) }
let_it_be(:project) { create(:project, namespace: authorized_user.namespace) } let_it_be(:project) { create(:project, namespace: authorized_user.namespace) }
let_it_be(:public_issue) { create(:issue, project: project) } let_it_be(:public_issue) { create(:issue, project: project) }
let_it_be(:confidential_issue) { create(:issue, project: project, confidential: true) } let_it_be(:confidential_issue) { create(:issue, project: project, confidential: true) }
let_it_be(:hidden_issue) { create(:issue, project: project, author: banned_user) }
context 'when no project filter is given' do context 'when no project filter is given' do
let(:params) { {} } let(:params) { {} }
...@@ -250,7 +252,7 @@ RSpec.describe IssuesFinder do ...@@ -250,7 +252,7 @@ RSpec.describe IssuesFinder do
subject { described_class.new(auditor_user, params).with_confidentiality_access_check } subject { described_class.new(auditor_user, params).with_confidentiality_access_check }
it 'returns all issues' do it 'returns all issues' do
expect(subject).to include(public_issue, confidential_issue) expect(subject).to include(public_issue, confidential_issue, hidden_issue)
end end
end end
end end
...@@ -264,7 +266,7 @@ RSpec.describe IssuesFinder do ...@@ -264,7 +266,7 @@ RSpec.describe IssuesFinder do
subject { described_class.new(auditor_user, params).with_confidentiality_access_check } subject { described_class.new(auditor_user, params).with_confidentiality_access_check }
it 'returns all issues' do it 'returns all issues' do
expect(subject).to include(public_issue, confidential_issue) expect(subject).to include(public_issue, confidential_issue, hidden_issue)
end end
end end
end end
......
...@@ -2571,12 +2571,6 @@ msgstr "" ...@@ -2571,12 +2571,6 @@ msgstr ""
msgid "AdminUsers|Blocking user has the following effects:" msgid "AdminUsers|Blocking user has the following effects:"
msgstr "" msgstr ""
msgid "AdminUsers|Can't access Git repositories."
msgstr ""
msgid "AdminUsers|Can't log in."
msgstr ""
msgid "AdminUsers|Cannot sign in or access instance information" msgid "AdminUsers|Cannot sign in or access instance information"
msgstr "" msgstr ""
...@@ -2643,6 +2637,9 @@ msgstr "" ...@@ -2643,6 +2637,9 @@ msgstr ""
msgid "AdminUsers|Is using seat" msgid "AdminUsers|Is using seat"
msgstr "" msgstr ""
msgid "AdminUsers|Issues authored by this user are hidden from other users."
msgstr ""
msgid "AdminUsers|It's you!" msgid "AdminUsers|It's you!"
msgstr "" msgstr ""
...@@ -2706,6 +2703,12 @@ msgstr "" ...@@ -2706,6 +2703,12 @@ msgstr ""
msgid "AdminUsers|Sort by" msgid "AdminUsers|Sort by"
msgstr "" msgstr ""
msgid "AdminUsers|The user can't access git repositories."
msgstr ""
msgid "AdminUsers|The user can't log in."
msgstr ""
msgid "AdminUsers|The user will be logged out" msgid "AdminUsers|The user will be logged out"
msgstr "" msgstr ""
...@@ -2772,7 +2775,7 @@ msgstr "" ...@@ -2772,7 +2775,7 @@ msgstr ""
msgid "AdminUsers|What does this mean?" msgid "AdminUsers|What does this mean?"
msgstr "" msgstr ""
msgid "AdminUsers|When banned, users:" msgid "AdminUsers|When banned:"
msgstr "" msgstr ""
msgid "AdminUsers|When the user logs back in, their account will reactivate as a fully active account" msgid "AdminUsers|When the user logs back in, their account will reactivate as a fully active account"
...@@ -34029,6 +34032,9 @@ msgstr "" ...@@ -34029,6 +34032,9 @@ msgstr ""
msgid "This issue is currently blocked by the following issues:" msgid "This issue is currently blocked by the following issues:"
msgstr "" msgstr ""
msgid "This issue is hidden because its author has been banned"
msgstr ""
msgid "This issue is in a child epic of the filtered epic" msgid "This issue is in a child epic of the filtered epic"
msgstr "" msgstr ""
......
...@@ -789,7 +789,7 @@ RSpec.describe IssuesFinder do ...@@ -789,7 +789,7 @@ RSpec.describe IssuesFinder do
context 'user filters confidential issues' do context 'user filters confidential issues' do
let(:params) { { confidential: true } } let(:params) { { confidential: true } }
it 'returns only confdential issues' do it 'returns only confidential issues' do
expect(issues).to contain_exactly(confidential_issue) expect(issues).to contain_exactly(confidential_issue)
end end
end end
...@@ -797,7 +797,7 @@ RSpec.describe IssuesFinder do ...@@ -797,7 +797,7 @@ RSpec.describe IssuesFinder do
context 'user filters only public issues' do context 'user filters only public issues' do
let(:params) { { confidential: false } } let(:params) { { confidential: false } }
it 'returns only confdential issues' do it 'returns only public issues' do
expect(issues).to contain_exactly(issue1, issue2, issue3, issue4, issue5) expect(issues).to contain_exactly(issue1, issue2, issue3, issue4, issue5)
end end
end end
...@@ -1004,9 +1004,38 @@ RSpec.describe IssuesFinder do ...@@ -1004,9 +1004,38 @@ RSpec.describe IssuesFinder do
let(:guest) { create(:user) } let(:guest) { create(:user) }
let_it_be(:authorized_user) { create(:user) } let_it_be(:authorized_user) { create(:user) }
let_it_be(:banned_user) { create(:user, :banned) }
let_it_be(:project) { create(:project, namespace: authorized_user.namespace) } let_it_be(:project) { create(:project, namespace: authorized_user.namespace) }
let_it_be(:public_issue) { create(:issue, project: project) } let_it_be(:public_issue) { create(:issue, project: project) }
let_it_be(:confidential_issue) { create(:issue, project: project, confidential: true) } let_it_be(:confidential_issue) { create(:issue, project: project, confidential: true) }
let_it_be(:hidden_issue) { create(:issue, project: project, author: banned_user) }
shared_examples 'returns public, does not return hidden or confidential' do
it 'returns only public issues' do
expect(subject).to include(public_issue)
expect(subject).not_to include(confidential_issue, hidden_issue)
end
end
shared_examples 'returns public and confidential, does not return hidden' do
it 'returns only public and confidential issues' do
expect(subject).to include(public_issue, confidential_issue)
expect(subject).not_to include(hidden_issue)
end
end
shared_examples 'returns public and hidden, does not return confidential' do
it 'returns only public and hidden issues' do
expect(subject).to include(public_issue, hidden_issue)
expect(subject).not_to include(confidential_issue)
end
end
shared_examples 'returns public, confidential, and hidden' do
it 'returns all issues' do
expect(subject).to include(public_issue, confidential_issue, hidden_issue)
end
end
context 'when no project filter is given' do context 'when no project filter is given' do
let(:params) { {} } let(:params) { {} }
...@@ -1014,18 +1043,28 @@ RSpec.describe IssuesFinder do ...@@ -1014,18 +1043,28 @@ RSpec.describe IssuesFinder do
context 'for an anonymous user' do context 'for an anonymous user' do
subject { described_class.new(nil, params).with_confidentiality_access_check } subject { described_class.new(nil, params).with_confidentiality_access_check }
it 'returns only public issues' do it_behaves_like 'returns public, does not return hidden or confidential'
expect(subject).to include(public_issue)
expect(subject).not_to include(confidential_issue) context 'when feature flag is disabled' do
before do
stub_feature_flags(ban_user_feature_flag: false)
end
it_behaves_like 'returns public and hidden, does not return confidential'
end end
end end
context 'for a user without project membership' do context 'for a user without project membership' do
subject { described_class.new(user, params).with_confidentiality_access_check } subject { described_class.new(user, params).with_confidentiality_access_check }
it 'returns only public issues' do it_behaves_like 'returns public, does not return hidden or confidential'
expect(subject).to include(public_issue)
expect(subject).not_to include(confidential_issue) context 'when feature flag is disabled' do
before do
stub_feature_flags(ban_user_feature_flag: false)
end
it_behaves_like 'returns public and hidden, does not return confidential'
end end
end end
...@@ -1036,17 +1075,28 @@ RSpec.describe IssuesFinder do ...@@ -1036,17 +1075,28 @@ RSpec.describe IssuesFinder do
project.add_guest(guest) project.add_guest(guest)
end end
it 'returns only public issues' do it_behaves_like 'returns public, does not return hidden or confidential'
expect(subject).to include(public_issue)
expect(subject).not_to include(confidential_issue) context 'when feature flag is disabled' do
before do
stub_feature_flags(ban_user_feature_flag: false)
end
it_behaves_like 'returns public and hidden, does not return confidential'
end end
end end
context 'for a project member with access to view confidential issues' do context 'for a project member with access to view confidential issues' do
subject { described_class.new(authorized_user, params).with_confidentiality_access_check } subject { described_class.new(authorized_user, params).with_confidentiality_access_check }
it 'returns all issues' do it_behaves_like 'returns public and confidential, does not return hidden'
expect(subject).to include(public_issue, confidential_issue)
context 'when feature flag is disabled' do
before do
stub_feature_flags(ban_user_feature_flag: false)
end
it_behaves_like 'returns public, confidential, and hidden'
end end
end end
...@@ -1056,15 +1106,26 @@ RSpec.describe IssuesFinder do ...@@ -1056,15 +1106,26 @@ RSpec.describe IssuesFinder do
subject { described_class.new(admin_user, params).with_confidentiality_access_check } subject { described_class.new(admin_user, params).with_confidentiality_access_check }
context 'when admin mode is enabled', :enable_admin_mode do context 'when admin mode is enabled', :enable_admin_mode do
it 'returns all issues' do it_behaves_like 'returns public, confidential, and hidden'
expect(subject).to include(public_issue, confidential_issue)
context 'when feature flag is disabled' do
before do
stub_feature_flags(ban_user_feature_flag: false)
end
it_behaves_like 'returns public, confidential, and hidden'
end end
end end
context 'when admin mode is disabled' do context 'when admin mode is disabled' do
it 'returns only public issues' do it_behaves_like 'returns public, does not return hidden or confidential'
expect(subject).to include(public_issue)
expect(subject).not_to include(confidential_issue) context 'when feature flag is disabled' do
before do
stub_feature_flags(ban_user_feature_flag: false)
end
it_behaves_like 'returns public and hidden, does not return confidential'
end end
end end
end end
...@@ -1076,14 +1137,18 @@ RSpec.describe IssuesFinder do ...@@ -1076,14 +1137,18 @@ RSpec.describe IssuesFinder do
context 'for an anonymous user' do context 'for an anonymous user' do
subject { described_class.new(nil, params).with_confidentiality_access_check } subject { described_class.new(nil, params).with_confidentiality_access_check }
it 'returns only public issues' do it_behaves_like 'returns public, does not return hidden or confidential'
expect(subject).to include(public_issue)
expect(subject).not_to include(confidential_issue) context 'when feature flag is disabled' do
before do
stub_feature_flags(ban_user_feature_flag: false)
end
it_behaves_like 'returns public and hidden, does not return confidential'
end end
it 'does not filter by confidentiality' do it 'does not filter by confidentiality' do
expect(Issue).not_to receive(:where).with(a_string_matching('confidential'), anything) expect(Issue).not_to receive(:where).with(a_string_matching('confidential'), anything)
subject subject
end end
end end
...@@ -1091,9 +1156,14 @@ RSpec.describe IssuesFinder do ...@@ -1091,9 +1156,14 @@ RSpec.describe IssuesFinder do
context 'for a user without project membership' do context 'for a user without project membership' do
subject { described_class.new(user, params).with_confidentiality_access_check } subject { described_class.new(user, params).with_confidentiality_access_check }
it 'returns only public issues' do it_behaves_like 'returns public, does not return hidden or confidential'
expect(subject).to include(public_issue)
expect(subject).not_to include(confidential_issue) context 'when feature flag is disabled' do
before do
stub_feature_flags(ban_user_feature_flag: false)
end
it_behaves_like 'returns public and hidden, does not return confidential'
end end
it 'filters by confidentiality' do it 'filters by confidentiality' do
...@@ -1108,9 +1178,14 @@ RSpec.describe IssuesFinder do ...@@ -1108,9 +1178,14 @@ RSpec.describe IssuesFinder do
project.add_guest(guest) project.add_guest(guest)
end end
it 'returns only public issues' do it_behaves_like 'returns public, does not return hidden or confidential'
expect(subject).to include(public_issue)
expect(subject).not_to include(confidential_issue) context 'when feature flag is disabled' do
before do
stub_feature_flags(ban_user_feature_flag: false)
end
it_behaves_like 'returns public and hidden, does not return confidential'
end end
it 'filters by confidentiality' do it 'filters by confidentiality' do
...@@ -1121,8 +1196,14 @@ RSpec.describe IssuesFinder do ...@@ -1121,8 +1196,14 @@ RSpec.describe IssuesFinder do
context 'for a project member with access to view confidential issues' do context 'for a project member with access to view confidential issues' do
subject { described_class.new(authorized_user, params).with_confidentiality_access_check } subject { described_class.new(authorized_user, params).with_confidentiality_access_check }
it 'returns all issues' do it_behaves_like 'returns public and confidential, does not return hidden'
expect(subject).to include(public_issue, confidential_issue)
context 'when feature flag is disabled' do
before do
stub_feature_flags(ban_user_feature_flag: false)
end
it_behaves_like 'returns public, confidential, and hidden'
end end
it 'does not filter by confidentiality' do it 'does not filter by confidentiality' do
...@@ -1138,8 +1219,14 @@ RSpec.describe IssuesFinder do ...@@ -1138,8 +1219,14 @@ RSpec.describe IssuesFinder do
subject { described_class.new(admin_user, params).with_confidentiality_access_check } subject { described_class.new(admin_user, params).with_confidentiality_access_check }
context 'when admin mode is enabled', :enable_admin_mode do context 'when admin mode is enabled', :enable_admin_mode do
it 'returns all issues' do it_behaves_like 'returns public, confidential, and hidden'
expect(subject).to include(public_issue, confidential_issue)
context 'when feature flag is disabled' do
before do
stub_feature_flags(ban_user_feature_flag: false)
end
it_behaves_like 'returns public, confidential, and hidden'
end end
it 'does not filter by confidentiality' do it 'does not filter by confidentiality' do
...@@ -1150,9 +1237,14 @@ RSpec.describe IssuesFinder do ...@@ -1150,9 +1237,14 @@ RSpec.describe IssuesFinder do
end end
context 'when admin mode is disabled' do context 'when admin mode is disabled' do
it 'returns only public issues' do it_behaves_like 'returns public, does not return hidden or confidential'
expect(subject).to include(public_issue)
expect(subject).not_to include(confidential_issue) context 'when feature flag is disabled' do
before do
stub_feature_flags(ban_user_feature_flag: false)
end
it_behaves_like 'returns public and hidden, does not return confidential'
end end
it 'filters by confidentiality' do it 'filters by confidentiality' do
......
...@@ -86,9 +86,9 @@ RSpec.describe Mutations::DesignManagement::Delete do ...@@ -86,9 +86,9 @@ RSpec.describe Mutations::DesignManagement::Delete do
end end
end end
it 'runs no more than 28 queries' do it 'runs no more than 29 queries' do
filenames.each(&:present?) # ignore setup filenames.each(&:present?) # ignore setup
# Queries: as of 2019-08-28 # Queries: as of 2021-07-22
# ------------- # -------------
# 01. routing query # 01. routing query
# 02. find project by id # 02. find project by id
...@@ -100,25 +100,26 @@ RSpec.describe Mutations::DesignManagement::Delete do ...@@ -100,25 +100,26 @@ RSpec.describe Mutations::DesignManagement::Delete do
# 09. find namespace by id # 09. find namespace by id
# 10. find group namespace by id # 10. find group namespace by id
# 11. project.authorizations for user (same query as 5) # 11. project.authorizations for user (same query as 5)
# 12. project.project_features (same query as 3) # 12. find user by id
# 13. project.authorizations for user (same query as 5) # 13. project.project_features (same query as 3)
# 14. current designs by filename and issue # 14. project.authorizations for user (same query as 5)
# 15, 16 project.authorizations for user (same query as 5) # 15. current designs by filename and issue
# 17. find route by id and source_type # 16, 17 project.authorizations for user (same query as 5)
# 18. find route by id and source_type
# ------------- our queries are below: # ------------- our queries are below:
# 18. start transaction 1 # 19. start transaction 1
# 19. start transaction 2 # 20. start transaction 2
# 20. find version by sha and issue # 21. find version by sha and issue
# 21. exists version with sha and issue? # 22. exists version with sha and issue?
# 22. leave transaction 2 # 23. leave transaction 2
# 23. create version with sha and issue # 24. create version with sha and issue
# 24. create design-version links # 25. create design-version links
# 25. validate version.actions.present? # 26. validate version.actions.present?
# 26. validate version.issue.present? # 27. validate version.issue.present?
# 27. validate version.sha is unique # 28. validate version.sha is unique
# 28. leave transaction 1 # 29. leave transaction 1
# #
expect { run_mutation }.not_to exceed_query_limit(28) expect { run_mutation }.not_to exceed_query_limit(29)
end end
end end
......
...@@ -393,13 +393,13 @@ RSpec.describe Resolvers::IssuesResolver do ...@@ -393,13 +393,13 @@ RSpec.describe Resolvers::IssuesResolver do
end end
it 'finds a specific issue with iid', :request_store do it 'finds a specific issue with iid', :request_store do
result = batch_sync(max_queries: 4) { resolve_issues(iid: issue1.iid).to_a } result = batch_sync(max_queries: 5) { resolve_issues(iid: issue1.iid).to_a }
expect(result).to contain_exactly(issue1) expect(result).to contain_exactly(issue1)
end end
it 'batches queries that only include IIDs', :request_store do it 'batches queries that only include IIDs', :request_store do
result = batch_sync(max_queries: 4) do result = batch_sync(max_queries: 5) do
[issue1, issue2] [issue1, issue2]
.map { |issue| resolve_issues(iid: issue.iid.to_s) } .map { |issue| resolve_issues(iid: issue.iid.to_s) }
.flat_map(&:to_a) .flat_map(&:to_a)
...@@ -409,7 +409,7 @@ RSpec.describe Resolvers::IssuesResolver do ...@@ -409,7 +409,7 @@ RSpec.describe Resolvers::IssuesResolver do
end end
it 'finds a specific issue with iids', :request_store do it 'finds a specific issue with iids', :request_store do
result = batch_sync(max_queries: 4) do result = batch_sync(max_queries: 5) do
resolve_issues(iids: [issue1.iid]).to_a resolve_issues(iids: [issue1.iid]).to_a
end end
......
...@@ -796,17 +796,47 @@ RSpec.describe Issue do ...@@ -796,17 +796,47 @@ RSpec.describe Issue do
end end
end end
shared_examples 'hidden issue readable by user' do
before do
issue.author.ban!
end
specify do
is_expected.to eq(true)
end
after do
issue.author.activate!
end
end
shared_examples 'hidden issue not readable by user' do
before do
issue.author.ban!
end
specify do
is_expected.to eq(false)
end
after do
issue.author.activate!
end
end
context 'with an admin user' do context 'with an admin user' do
let(:user) { build(:admin) } let(:user) { build(:admin) }
context 'when admin mode is enabled', :enable_admin_mode do context 'when admin mode is enabled', :enable_admin_mode do
it_behaves_like 'issue readable by user' it_behaves_like 'issue readable by user'
it_behaves_like 'confidential issue readable by user' it_behaves_like 'confidential issue readable by user'
it_behaves_like 'hidden issue readable by user'
end end
context 'when admin mode is disabled' do context 'when admin mode is disabled' do
it_behaves_like 'issue not readable by user' it_behaves_like 'issue not readable by user'
it_behaves_like 'confidential issue not readable by user' it_behaves_like 'confidential issue not readable by user'
it_behaves_like 'hidden issue not readable by user'
end end
end end
...@@ -817,6 +847,7 @@ RSpec.describe Issue do ...@@ -817,6 +847,7 @@ RSpec.describe Issue do
it_behaves_like 'issue readable by user' it_behaves_like 'issue readable by user'
it_behaves_like 'confidential issue readable by user' it_behaves_like 'confidential issue readable by user'
it_behaves_like 'hidden issue not readable by user'
end end
context 'with a reporter user' do context 'with a reporter user' do
...@@ -826,6 +857,7 @@ RSpec.describe Issue do ...@@ -826,6 +857,7 @@ RSpec.describe Issue do
it_behaves_like 'issue readable by user' it_behaves_like 'issue readable by user'
it_behaves_like 'confidential issue readable by user' it_behaves_like 'confidential issue readable by user'
it_behaves_like 'hidden issue not readable by user'
end end
context 'with a guest user' do context 'with a guest user' do
...@@ -835,6 +867,7 @@ RSpec.describe Issue do ...@@ -835,6 +867,7 @@ RSpec.describe Issue do
it_behaves_like 'issue readable by user' it_behaves_like 'issue readable by user'
it_behaves_like 'confidential issue not readable by user' it_behaves_like 'confidential issue not readable by user'
it_behaves_like 'hidden issue not readable by user'
context 'when user is an assignee' do context 'when user is an assignee' do
before do before do
...@@ -843,6 +876,7 @@ RSpec.describe Issue do ...@@ -843,6 +876,7 @@ RSpec.describe Issue do
it_behaves_like 'issue readable by user' it_behaves_like 'issue readable by user'
it_behaves_like 'confidential issue readable by user' it_behaves_like 'confidential issue readable by user'
it_behaves_like 'hidden issue not readable by user'
end end
context 'when user is the author' do context 'when user is the author' do
...@@ -852,6 +886,7 @@ RSpec.describe Issue do ...@@ -852,6 +886,7 @@ RSpec.describe Issue do
it_behaves_like 'issue readable by user' it_behaves_like 'issue readable by user'
it_behaves_like 'confidential issue readable by user' it_behaves_like 'confidential issue readable by user'
it_behaves_like 'hidden issue not readable by user'
end end
end end
...@@ -861,6 +896,7 @@ RSpec.describe Issue do ...@@ -861,6 +896,7 @@ RSpec.describe Issue do
it_behaves_like 'issue readable by user' it_behaves_like 'issue readable by user'
it_behaves_like 'confidential issue not readable by user' it_behaves_like 'confidential issue not readable by user'
it_behaves_like 'hidden issue not readable by user'
end end
context 'using an internal project' do context 'using an internal project' do
...@@ -873,6 +909,7 @@ RSpec.describe Issue do ...@@ -873,6 +909,7 @@ RSpec.describe Issue do
it_behaves_like 'issue readable by user' it_behaves_like 'issue readable by user'
it_behaves_like 'confidential issue not readable by user' it_behaves_like 'confidential issue not readable by user'
it_behaves_like 'hidden issue not readable by user'
end end
context 'using an external user' do context 'using an external user' do
...@@ -882,6 +919,7 @@ RSpec.describe Issue do ...@@ -882,6 +919,7 @@ RSpec.describe Issue do
it_behaves_like 'issue not readable by user' it_behaves_like 'issue not readable by user'
it_behaves_like 'confidential issue not readable by user' it_behaves_like 'confidential issue not readable by user'
it_behaves_like 'hidden issue not readable by user'
end end
end end
...@@ -892,6 +930,7 @@ RSpec.describe Issue do ...@@ -892,6 +930,7 @@ RSpec.describe Issue do
it_behaves_like 'issue not readable by user' it_behaves_like 'issue not readable by user'
it_behaves_like 'confidential issue not readable by user' it_behaves_like 'confidential issue not readable by user'
it_behaves_like 'hidden issue not readable by user'
end end
end end
...@@ -1160,6 +1199,26 @@ RSpec.describe Issue do ...@@ -1160,6 +1199,26 @@ RSpec.describe Issue do
end end
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 eq([public_issue, hidden_issue])
end
end
end
describe '.by_project_id_and_iid' do describe '.by_project_id_and_iid' do
let_it_be(:issue_a) { create(:issue, project: reusable_project) } let_it_be(:issue_a) { create(:issue, project: reusable_project) }
let_it_be(:issue_b) { create(:issue, iid: issue_a.iid) } let_it_be(:issue_b) { create(:issue, iid: issue_a.iid) }
......
...@@ -360,6 +360,21 @@ RSpec.describe IssuePolicy do ...@@ -360,6 +360,21 @@ RSpec.describe IssuePolicy do
expect(permissions(assignee, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata) expect(permissions(assignee, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata)
end end
end end
context 'with a hidden issue' do
let(:user) { create(:user) }
let(:banned_user) { create(:user, :banned) }
let(:admin) { create(:user, :admin)}
let(:hidden_issue) { create(:issue, project: project, author: banned_user) }
it 'does not allow non-admin user to read the issue' do
expect(permissions(user, hidden_issue)).not_to be_allowed(:read_issue)
end
it 'allows admin to read the issue', :enable_admin_mode do
expect(permissions(admin, hidden_issue)).to be_allowed(:read_issue)
end
end
end end
context 'with external authorization enabled' do context 'with external authorization enabled' do
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment