Fix RfC Visibility to work as intended

The RfC Visibility of a consumer is intended to restrict who can access which RfCs. So far, those restrictions were only applied one way, for learners of a restricted consumer to view other (external) RfCs. However, the other way around should also work: If a RfC was created as part of a restricted consumer, no other external user should be able to interfere with this RfC. This commit, therefore, adds this direction as well and covers both directions with tests.
This commit is contained in:
Sebastian Serth
2023-07-13 10:51:10 +02:00
parent c6977b6319
commit e097036296
3 changed files with 207 additions and 99 deletions

View File

@ -6,7 +6,7 @@ class RequestForCommentPolicy < ApplicationPolicy
end end
def show? def show?
admin? || rfc_visibility admin? || author? || rfc_visibility
end end
def destroy? def destroy?
@ -14,11 +14,11 @@ class RequestForCommentPolicy < ApplicationPolicy
end end
def mark_as_solved? def mark_as_solved?
admin? || (author? && rfc_visibility) admin? || author?
end end
def set_thank_you_note? def set_thank_you_note?
admin? || (author? && rfc_visibility) admin? || author?
end end
def clear_question? def clear_question?
@ -42,12 +42,16 @@ class RequestForCommentPolicy < ApplicationPolicy
end end
def rfc_visibility def rfc_visibility
case @user.consumer.rfc_visibility # The consumer with the most restricted visibility determines the visibility of the RfC
when 'all' case [@user.consumer.rfc_visibility, @record.author.consumer.rfc_visibility]
# Only if both consumers allow learners to see all RfCs, the RfC is visible to the learner
when %w[all all]
everyone everyone
when 'consumer' # At least one consumer limits the visibility to the consumer
when %w[consumer all], %w[all consumer], %w[consumer consumer]
@record.author.consumer == @user.consumer @record.author.consumer == @user.consumer
when 'study_group' # At least one consumer limits the visibility to the study group
when %w[study_group all], %w[all study_group], %w[study_group consumer], %w[consumer study_group], %w[study_group study_group]
@record.submission.study_group.present? && @record.submission.study_group.id == @user.current_study_group_id @record.submission.study_group.present? && @record.submission.study_group.id == @user.current_study_group_id
else else
raise "Unknown RfC Visibility #{current_user.consumer.rfc_visibility}" raise "Unknown RfC Visibility #{current_user.consumer.rfc_visibility}"
@ -61,8 +65,16 @@ class RequestForCommentPolicy < ApplicationPolicy
else else
case @user.consumer.rfc_visibility case @user.consumer.rfc_visibility
when 'all' when 'all'
@scope.all # We need to filter those RfCs where the visibility is more restricted than the `all` visibility.
rfcs_with_users = @scope
.joins('LEFT OUTER JOIN external_users ON request_for_comments.user_type = \'ExternalUser\' AND request_for_comments.user_id = external_users.id')
.joins('LEFT OUTER JOIN internal_users ON request_for_comments.user_type = \'InternalUser\' AND request_for_comments.user_id = internal_users.id')
rfcs_with_users.where(external_users: {consumer_id: Consumer.rfc_visibility_all})
.or(rfcs_with_users.where(internal_users: {consumer_id: Consumer.rfc_visibility_all}))
when 'consumer' when 'consumer'
# Since the `rfc_visibility` is set on a consumer level, we do not need to consider the `study_group` visibility here.
# Therefore, those RfCs where the author is limited to study group RfCs definitely belong to another consumer.
rfcs_with_users = @scope rfcs_with_users = @scope
.joins('LEFT OUTER JOIN external_users ON request_for_comments.user_type = \'ExternalUser\' AND request_for_comments.user_id = external_users.id') .joins('LEFT OUTER JOIN external_users ON request_for_comments.user_type = \'ExternalUser\' AND request_for_comments.user_id = external_users.id')
.joins('LEFT OUTER JOIN internal_users ON request_for_comments.user_type = \'InternalUser\' AND request_for_comments.user_id = internal_users.id') .joins('LEFT OUTER JOIN internal_users ON request_for_comments.user_type = \'InternalUser\' AND request_for_comments.user_id = internal_users.id')
@ -70,6 +82,7 @@ class RequestForCommentPolicy < ApplicationPolicy
rfcs_with_users.where(external_users: {consumer_id: @user.consumer.id}) rfcs_with_users.where(external_users: {consumer_id: @user.consumer.id})
.or(rfcs_with_users.where(internal_users: {consumer_id: @user.consumer.id})) .or(rfcs_with_users.where(internal_users: {consumer_id: @user.consumer.id}))
when 'study_group' when 'study_group'
# Since the `rfc_visibility` is already the most restricted visibility, we do not need to consider any other visibility here.
@scope @scope
.joins(:submission) .joins(:submission)
.where(submission: {study_group: @user.current_study_group_id}) .where(submission: {study_group: @user.current_study_group_id})

View File

@ -4,7 +4,7 @@ FactoryBot.define do
factory :rfc, class: 'RequestForComment' do factory :rfc, class: 'RequestForComment' do
user factory: :external_user user factory: :external_user
exercise factory: :math exercise factory: :math
submission { association :submission, exercise:, user: } submission { association :submission, exercise:, user:, study_group: user&.study_groups&.first }
file file
sequence :question do |n| sequence :question do |n|
"test question #{n}" "test question #{n}"

View File

@ -49,150 +49,245 @@ describe RequestForCommentPolicy do
end end
context 'when the RfC visibility is considered' do context 'when the RfC visibility is considered' do
let(:user) { create(:learner, consumer:) } shared_examples 'grants access to everyone' do
it 'grants access to everyone' do
%i[external_user teacher admin].each do |factory_name|
expect(policy).to permit(create(factory_name, consumer: viewer_consumer, study_groups: viewer_study_groups), rfc)
end
end
let(:rfc_other_consumer) do it 'grants access to authors' do
rfc = create(:rfc, user:) expect(policy).to permit(rfc.author, rfc)
rfc.user.update(consumer: create(:consumer, name: 'Other Consumer')) end
rfc
end end
let(:rfc_other_study_group) do shared_examples 'grants access to admins and authors only' do
rfc = create(:rfc, user:) it 'grants access to admins' do
rfc.user.update(consumer: user.consumer) expect(policy).to permit(create(:admin, consumer: viewer_consumer, study_groups: viewer_study_groups), rfc)
rfc.submission.update(study_group: create(:study_group)) end
rfc
it 'grants access to authors' do
expect(policy).to permit(rfc.author, rfc)
end
it 'does not grant access to all other users' do
%i[external_user teacher].each do |factory_name|
expect(policy).not_to permit(create(factory_name, consumer: viewer_consumer, study_groups: viewer_study_groups), rfc)
end
end
end end
let(:rfc_peer) do let(:rfc_author) { create(:learner, consumer: author_consumer, study_groups: author_study_groups) }
rfc = create(:rfc, user:) let(:author_study_groups) { create_list(:study_group, 1, consumer: author_consumer) }
rfc.user.update(consumer: user.consumer) let(:rfc) { create(:rfc, user: rfc_author) }
rfc.submission.update(study_group: user.study_groups.first)
rfc
end
let(:all_rfcs) { [rfc_other_consumer, rfc_other_study_group, rfc_peer] } context "when the author's rfc_visibility is set to all" do
let(:same_consumer_rfcs) { [rfc_other_study_group, rfc_peer] } let(:author_consumer) { create(:consumer, rfc_visibility: 'all') }
let(:other_study_groups_rfcs) { [rfc_other_consumer, rfc_other_study_group] }
context 'when rfc_visibility is set to all' do context 'when the viewer is from another consumer' do
let(:consumer) { create(:consumer, rfc_visibility: 'all') } context "when the viewer's rfc_visibility is set to all" do
let(:viewer_consumer) { create(:consumer, name: 'Other Consumer', rfc_visibility: 'all') }
let(:viewer_study_groups) { create_list(:study_group, 1, consumer: viewer_consumer) }
permissions(:show?) do permissions(:show?) do
it 'grants access to everyone' do include_examples 'grants access to everyone'
%i[external_user teacher admin].each do |factory_name| end
all_rfcs.each do |specific_rfc|
expect(policy).to permit(create(factory_name, consumer:), specific_rfc) %i[mark_as_solved? set_thank_you_note?].each do |action|
permissions(action) do
include_examples 'grants access to admins and authors only'
end
end
end
context "when the viewer's rfc_visibility is set to consumer" do
let(:viewer_consumer) { create(:consumer, name: 'Other Consumer', rfc_visibility: 'consumer') }
let(:viewer_study_groups) { create_list(:study_group, 1, consumer: viewer_consumer) }
%i[mark_as_solved? set_thank_you_note? show?].each do |action|
permissions(action) do
include_examples 'grants access to admins and authors only'
end
end
end
context "when the viewer's rfc_visibility is set to study_group" do
let(:viewer_consumer) { create(:consumer, name: 'Other Consumer', rfc_visibility: 'study_group') }
let(:viewer_study_groups) { create_list(:study_group, 1, consumer: viewer_consumer) }
%i[mark_as_solved? set_thank_you_note? show?].each do |action|
permissions(action) do
include_examples 'grants access to admins and authors only'
end end
end end
end end
end end
%i[mark_as_solved? set_thank_you_note?].each do |action| context 'when the viewer is from the same consumer' do
permissions(action) do let(:viewer_consumer) { author_consumer }
it 'grants access to admins' do
all_rfcs.each do |specific_rfc| context 'when the viewer is from another study group' do
expect(policy).to permit(create(:admin, consumer:), specific_rfc) let(:viewer_study_groups) { create_list(:study_group, 1, consumer: viewer_consumer) }
end
permissions(:show?) do
include_examples 'grants access to everyone'
end end
it 'grants access to authors' do %i[mark_as_solved? set_thank_you_note?].each do |action|
all_rfcs.each do |specific_rfc| permissions(action) do
expect(policy).to permit(specific_rfc.author, specific_rfc) include_examples 'grants access to admins and authors only'
end end
end end
end
it 'does not grant access to all other users' do context 'when the viewer is from the same study group' do
%i[external_user teacher].each do |factory_name| let(:viewer_study_groups) { author_study_groups }
all_rfcs.each do |specific_rfc|
expect(policy).not_to permit(create(factory_name, consumer:), specific_rfc) permissions(:show?) do
end include_examples 'grants access to everyone'
end
%i[mark_as_solved? set_thank_you_note?].each do |action|
permissions(action) do
include_examples 'grants access to admins and authors only'
end end
end end
end end
end end
end end
context 'when rfc_visibility is set to consumer' do context "when the author's rfc_visibility is set to consumer" do
let(:consumer) { create(:consumer, rfc_visibility: 'consumer') } let(:author_consumer) { create(:consumer, rfc_visibility: 'consumer') }
permissions(:show?) do context 'when the viewer is from another consumer' do
it 'grants access to admins' do context "when the viewer's rfc_visibility is set to all" do
all_rfcs.each do |specific_rfc| let(:viewer_consumer) { create(:consumer, name: 'Other Consumer', rfc_visibility: 'all') }
expect(policy).to permit(create(:admin, consumer:), specific_rfc) let(:viewer_study_groups) { create_list(:study_group, 1, consumer: viewer_consumer) }
end
end
it 'grants access to users from the same consumer' do %i[mark_as_solved? set_thank_you_note? show?].each do |action|
%i[external_user teacher].each do |factory_name| permissions(action) do
same_consumer_rfcs.each do |specific_rfc| include_examples 'grants access to admins and authors only'
expect(policy).to permit(create(factory_name, consumer:), specific_rfc)
end end
end end
end end
it 'does not grant access to users from other consumers' do context "when the viewer's rfc_visibility is set to consumer" do
%i[external_user teacher].each do |factory_name| let(:viewer_consumer) { create(:consumer, name: 'Other Consumer', rfc_visibility: 'consumer') }
expect(policy).not_to permit(create(factory_name, consumer:), rfc_other_consumer) let(:viewer_study_groups) { create_list(:study_group, 1, consumer: viewer_consumer) }
%i[mark_as_solved? set_thank_you_note? show?].each do |action|
permissions(action) do
include_examples 'grants access to admins and authors only'
end
end
end
context "when the viewer's rfc_visibility is set to study_group" do
let(:viewer_consumer) { create(:consumer, name: 'Other Consumer', rfc_visibility: 'study_group') }
let(:viewer_study_groups) { create_list(:study_group, 1, consumer: viewer_consumer) }
%i[mark_as_solved? set_thank_you_note? show?].each do |action|
permissions(action) do
include_examples 'grants access to admins and authors only'
end
end end
end end
end end
# Testing `mark_as_solved?` and `set_thank_you_note?` is not necessary here, context 'when the viewer is from the same consumer' do
# because an author of an RfC can only be a user from the same consumer. let(:viewer_consumer) { author_consumer }
context 'when the viewer is from another study group' do
let(:viewer_study_groups) { create_list(:study_group, 1, consumer: viewer_consumer) }
permissions(:show?) do
include_examples 'grants access to everyone'
end
%i[mark_as_solved? set_thank_you_note?].each do |action|
permissions(action) do
include_examples 'grants access to admins and authors only'
end
end
end
context 'when the viewer is from the same study group' do
let(:viewer_study_groups) { author_study_groups }
permissions(:show?) do
include_examples 'grants access to everyone'
end
%i[mark_as_solved? set_thank_you_note?].each do |action|
permissions(action) do
include_examples 'grants access to admins and authors only'
end
end
end
end
end end
context 'when rfc_visibility is set to study_group' do context "when the author's rfc_visibility is set to study_group" do
let(:consumer) { create(:consumer, rfc_visibility: 'study_group') } let(:author_consumer) { create(:consumer, rfc_visibility: 'study_group') }
permissions(:show?) do context 'when the viewer is from another consumer' do
it 'grants access to admins' do context "when the viewer's rfc_visibility is set to all" do
all_rfcs.each do |specific_rfc| let(:viewer_consumer) { create(:consumer, name: 'Other Consumer', rfc_visibility: 'all') }
expect(policy).to permit(create(:admin, consumer:), specific_rfc) let(:viewer_study_groups) { create_list(:study_group, 1, consumer: viewer_consumer) }
%i[mark_as_solved? set_thank_you_note? show?].each do |action|
permissions(action) do
include_examples 'grants access to admins and authors only'
end
end end
end end
it 'grants access to users from the same study group' do context "when the viewer's rfc_visibility is set to consumer" do
%i[external_user teacher].each do |factory_name| let(:viewer_consumer) { create(:consumer, name: 'Other Consumer', rfc_visibility: 'consumer') }
expect(policy).to permit(create(factory_name, consumer:, study_groups: [rfc_peer.submission.study_group]), rfc_peer) let(:viewer_study_groups) { create_list(:study_group, 1, consumer: viewer_consumer) }
%i[mark_as_solved? set_thank_you_note? show?].each do |action|
permissions(action) do
include_examples 'grants access to admins and authors only'
end
end end
end end
it 'does not grant access to users from other consumers' do context "when the viewer's rfc_visibility is set to study_group" do
%i[external_user teacher].each do |factory_name| let(:viewer_consumer) { create(:consumer, name: 'Other Consumer', rfc_visibility: 'study_group') }
other_study_groups_rfcs.each do |specific_rfc| let(:viewer_study_groups) { create_list(:study_group, 1, consumer: viewer_consumer) }
expect(policy).not_to permit(create(factory_name, consumer:), specific_rfc)
%i[mark_as_solved? set_thank_you_note? show?].each do |action|
permissions(action) do
include_examples 'grants access to admins and authors only'
end end
end end
end end
end end
%i[mark_as_solved? set_thank_you_note?].each do |action| context 'when the viewer is from the same consumer' do
permissions(action) do let(:viewer_consumer) { author_consumer }
it 'grants access to admins' do
all_rfcs.each do |specific_rfc| context 'when the viewer is from another study group' do
expect(policy).to permit(create(:admin, consumer:), specific_rfc) let(:viewer_study_groups) { create_list(:study_group, 1, consumer: viewer_consumer) }
%i[mark_as_solved? set_thank_you_note? show?].each do |action|
permissions(action) do
include_examples 'grants access to admins and authors only'
end end
end end
end
# Testing `mark_as_solved?` and `set_thank_you_note?` with another consumer is not context 'when the viewer is from the same study group' do
# necessary here, because an author of an RfC can only be a user from the same consumer. let(:viewer_study_groups) { author_study_groups }
it 'grants access to authors of the same primary study group' do permissions(:show?) do
rfc_peer.author.update(study_groups: [rfc_peer.submission.study_group]) include_examples 'grants access to everyone'
expect(policy).to permit(rfc_peer.author, rfc_peer)
end end
it 'does not grant access to authors of another primary study groups' do %i[mark_as_solved? set_thank_you_note?].each do |action|
rfc_other_study_group.author.update(study_groups: create_list(:study_group, 1)) permissions(action) do
expect(policy).not_to permit(rfc_other_study_group.author, rfc_other_study_group) include_examples 'grants access to admins and authors only'
end
it 'does not grant access to all other users' do
%i[external_user teacher].each do |factory_name|
all_rfcs.each do |specific_rfc|
expect(policy).not_to permit(create(factory_name, consumer:), specific_rfc)
end
end end
end end
end end