Add consumer-based RfC Visibility settings

This setting will be useful to increase data protection, where users might not be allowed to see RfCs from other contexts.
This commit is contained in:
Sebastian Serth
2023-02-20 09:35:21 +01:00
parent abede713f9
commit 9c3392b324
14 changed files with 146 additions and 14 deletions

View File

@ -23,7 +23,7 @@ class ConsumersController < ApplicationController
end
def consumer_params
params[:consumer].permit(:name, :oauth_key, :oauth_secret) if params[:consumer].present?
params[:consumer].permit(:name, :oauth_key, :oauth_secret, :rfc_visibility) if params[:consumer].present?
end
private :consumer_params

View File

@ -15,7 +15,7 @@ class RequestForCommentsController < ApplicationController
# GET /request_for_comments
# GET /request_for_comments.json
def index
@search = RequestForComment
@search = policy_scope(RequestForComment)
.last_per_user(2)
.with_last_activity
.ransack(params[:q])
@ -31,7 +31,7 @@ class RequestForCommentsController < ApplicationController
# GET /my_request_for_comments
def my_comment_requests
@search = RequestForComment
@search = policy_scope(RequestForComment)
.with_last_activity
.where(user: current_user)
.ransack(params[:q])
@ -44,7 +44,7 @@ class RequestForCommentsController < ApplicationController
# GET /my_rfc_activity
def rfcs_with_my_comments
@search = RequestForComment
@search = policy_scope(RequestForComment)
.with_last_activity
.joins(:comments) # we don't need to outer join here, because we know the user has commented on these
.where(comments: {user_id: current_user.id})
@ -59,7 +59,7 @@ class RequestForCommentsController < ApplicationController
# GET /exercises/:id/request_for_comments
def rfcs_for_exercise
exercise = Exercise.find(params[:exercise_id])
@search = RequestForComment
@search = policy_scope(RequestForComment)
.with_last_activity
.where(exercise_id: exercise.id)
.ransack(params[:q])
@ -155,8 +155,14 @@ class RequestForCommentsController < ApplicationController
# The study groups are grouped by the current study group and other study groups of the user
def set_study_group_grouping
current_study_group = StudyGroup.find_by(id: session[:study_group_id])
my_study_groups = current_user.study_groups.reject {|group| group == current_study_group }
my_study_groups = case current_user.consumer.rfc_visibility
when 'all' then current_user.study_groups.order(name: :desc)
when 'consumer' then current_user.study_groups.where(consumer: current_user.consumer).order(name: :desc)
when 'study_group' then current_study_group.present? ? Array(current_study_group) : []
else raise "Unknown RfC Visibility #{current_user.consumer.rfc_visibility}"
end
@study_groups_grouping = [[t('request_for_comments.index.study_groups.current'), Array(current_study_group)],
[t('request_for_comments.index.study_groups.my'), my_study_groups]]
[t('request_for_comments.index.study_groups.my'), my_study_groups.reject {|group| group == current_study_group }]]
end
end

View File

@ -1,6 +1,12 @@
# frozen_string_literal: true
class Consumer < ApplicationRecord
enum rfc_visibility: {
all: 0,
consumer: 1,
study_group: 2,
}, _default: :all, _prefix: true
has_many :users
has_many :study_groups, dependent: :destroy

View File

@ -88,7 +88,20 @@ class RequestForComment < ApplicationRecord
private
def row_number_user_sql
select('id, user_id, user_type, exercise_id, file_id, question, created_at, updated_at, solved, full_score_reached, submission_id, row_number() OVER (PARTITION BY user_id, user_type ORDER BY created_at DESC) as row_number')
select('
request_for_comments.id,
request_for_comments.user_id,
request_for_comments.user_type,
request_for_comments.exercise_id,
request_for_comments.file_id,
request_for_comments.question,
request_for_comments.created_at,
request_for_comments.updated_at,
request_for_comments.solved,
request_for_comments.full_score_reached,
request_for_comments.submission_id,
row_number() OVER (PARTITION BY request_for_comments.user_id, request_for_comments.user_type ORDER BY request_for_comments.created_at DESC) as row_number
')
end
end
end

View File

@ -48,4 +48,30 @@ class RequestForCommentPolicy < ApplicationPolicy
def rfcs_with_my_comments?
everyone
end
class Scope < Scope
def resolve
if @user.admin?
@scope.all
else
case @user.consumer.rfc_visibility
when 'all'
@scope.all
when 'consumer'
rfcs_with_users = @scope.distinct
.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: @user.consumer.id})
.or(rfcs_with_users.where(internal_users: {consumer_id: @user.consumer.id}))
when 'study_group'
@scope.distinct
.joins(:submission)
.where(submission: {study_group: @user.current_study_group_id})
else
@scope.none
end
end
end
end
end

View File

@ -9,4 +9,7 @@
.mb-3
= f.label(:oauth_secret, class: 'form-label')
= f.text_field(:oauth_secret, class: 'form-control', required: true)
.mb-3
= f.label(:rfc_visibility, class: 'form-label')
= f.collection_select(:rfc_visibility, Consumer.rfc_visibilities.map { |rfc_visibility, _id| [t("activerecord.attributes.consumer.rfc_visibility_type.#{rfc_visibility}"), rfc_visibility] }, :second, :first, {}, class: 'form-control form-control-sm')
.actions = render('shared/submit_button', f: f, object: @consumer)

View File

@ -6,5 +6,6 @@ h1
- %w[oauth_key oauth_secret].each do |attribute|
= row(label: "consumer.#{attribute}") do
= content_tag(:input, nil, class: 'form-control bg-secondary', readonly: true, value: @consumer.send(attribute))
= row(label: 'consumer.rfc_visibility', value: t("activerecord.attributes.consumer.rfc_visibility_type.#{@consumer.rfc_visibility}"))
= render('study_groups/table', study_groups: @consumer.study_groups.sort)

View File

@ -9,11 +9,12 @@ h1 = RequestForComment.model_name.human(count: 2)
.col-auto.mt-3.mt-md-0
= f.label(:title_cont, t('request_for_comments.solved'), class: 'visually-hidden form-label')
= f.select(:solved_not_eq, [[t('request_for_comments.show_all'), 2], [t('request_for_comments.show_unsolved'), 1], [t('request_for_comments.show_solved'), 0]])
.row
.col
= f.label(:submission_study_group_id_eq, t('request_for_comments.index.study_groups.placeholder'), class: 'visually-hidden form-label')
= f.grouped_collection_select(:submission_study_group_id_in, @study_groups_grouping, :second, :first, :id, :to_s, {},
{ class: 'form-control', multiple: true, "data-placeholder": t('request_for_comments.index.study_groups.placeholder') })
- unless current_user.consumer.rfc_visibility_study_group?
.row
.col
= f.label(:submission_study_group_id_eq, t('request_for_comments.index.study_groups.placeholder'), class: 'visually-hidden form-label')
= f.grouped_collection_select(:submission_study_group_id_in, @study_groups_grouping, :second, :first, :id, :to_s, {},
{ class: 'form-control', multiple: true, "data-placeholder": t('request_for_comments.index.study_groups.placeholder') })
.table-responsive
table.table.sortable.mt-4

View File

@ -7,6 +7,11 @@ de:
name: Name
oauth_key: OAuth Key
oauth_secret: OAuth Secret
rfc_visibility: Sichtbarkeit von Kommentaranfragen
rfc_visibility_type:
all: Alle Kommentaranfrage sind sichtbar
consumer: Nur Kommentaranfragen des aktuellen Konsumenten sind sichtbar
study_group: Nur Kommentaranfragen der aktuellen Lerngruppe sind sichtbar
execution_environment:
docker_image: Docker-Image
exposed_ports: Zugängliche Ports

View File

@ -7,6 +7,11 @@ en:
name: Name
oauth_key: OAuth Key
oauth_secret: OAuth Secret
rfc_visibility: Visibility of Request for Comments
rfc_visibility_type:
all: All Requests for Comments are visible
consumer: Only Requests for Comments of the current consumer are visible
study_group: Only Requests for Comments of the current study group are visible
execution_environment:
docker_image: Docker Image
exposed_ports: Exposed Ports

View File

@ -0,0 +1,7 @@
# frozen_string_literal: true
class AddRfcVisibilityToConsumer < ActiveRecord::Migration[7.0]
def change
add_column :consumers, :rfc_visibility, :integer, limit: 1, null: false, default: 0, comment: 'Used as enum in Rails'
end
end

View File

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[7.0].define(version: 2023_02_06_203117) do
ActiveRecord::Schema[7.0].define(version: 2023_02_19_113125) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_trgm"
enable_extension "pgcrypto"
@ -107,6 +107,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_02_06_203117) do
t.datetime "updated_at"
t.string "oauth_key"
t.string "oauth_secret"
t.integer "rfc_visibility", limit: 2, default: 0, null: false, comment: "Used as enum in Rails"
end
create_table "error_template_attributes", id: :serial, force: :cascade do |t|

View File

@ -9,6 +9,57 @@ describe RequestForCommentsController do
before { allow(controller).to receive(:current_user).and_return(user) }
shared_examples 'RfC visibility settings' do
let(:user) { create(:learner) }
let!(:rfc_other_consumer) do
rfc = create(:rfc)
rfc.user.update(consumer: create(:consumer, name: 'Other Consumer'))
rfc
end
let!(:rfc_other_study_group) do
rfc = create(:rfc)
rfc.user.update(consumer: user.consumer)
rfc.submission.update(study_group: create(:study_group))
rfc
end
let!(:rfc_peer) do
rfc = create(:rfc)
rfc.user.update(consumer: user.consumer)
rfc.submission.update(study_group: user.study_groups.first)
rfc
end
context 'when rfc_visibility is set to all' do
before { user.consumer.update(rfc_visibility: 'all') }
it 'shows all RfCs' do
get :index
expect(assigns(:request_for_comments)).to match_array([rfc_other_consumer, rfc_other_study_group, rfc_peer])
end
end
context 'when rfc_visibility is set to consumer' do
before { user.consumer.update(rfc_visibility: 'consumer') }
it 'shows only RfCs of the same consumer' do
get :index
expect(assigns(:request_for_comments)).to match_array([rfc_other_study_group, rfc_peer])
end
end
context 'when rfc_visibility is set to study_group' do
before { user.consumer.update(rfc_visibility: 'study_group') }
it 'shows only RfCs of the same study group' do
get :index
expect(assigns(:request_for_comments)).to match_array([rfc_peer])
end
end
end
describe 'GET #index' do
it 'renders the index template' do
get :index
@ -32,6 +83,8 @@ describe RequestForCommentsController do
expect(assigns(:request_for_comments)).to eq([rfc_within_my_study_group])
end
include_examples 'RfC visibility settings'
end
describe 'GET #my_comment_requests' do
@ -39,6 +92,8 @@ describe RequestForCommentsController do
expect_http_status(:ok)
expect_template(:index)
include_examples 'RfC visibility settings'
end
describe 'GET #rfcs_with_my_comments' do
@ -46,6 +101,8 @@ describe RequestForCommentsController do
expect_http_status(:ok)
expect_template(:index)
include_examples 'RfC visibility settings'
end
describe 'GET #rfcs_for_exercise' do

View File

@ -3,6 +3,7 @@
FactoryBot.define do
factory :consumer do
name { 'openHPI' }
rfc_visibility { 'all' }
singleton_consumer
end