From 5fb9975b1591f75c85a5ed43b67d4015a7128599 Mon Sep 17 00:00:00 2001 From: Nathan Richards Date: Wed, 19 Aug 2026 16:48:23 +0200 Subject: [PATCH 1/3] fix: allow students to view the Experience CS preview starter --- app/models/ability.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index 27cd52f59..6627f90b5 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -23,7 +23,12 @@ def initialize(user) private def define_common_non_student_abilities(user) - return if user&.student? + if user&.student? + # Allow students to view the Experience CS preview starter template. + can :show, Project, user_id: nil, school_id: nil, project_type: Project::Types::CODE_EDITOR_SCRATCH + can :show, Component, project: { user_id: nil, school_id: nil, project_type: Project::Types::CODE_EDITOR_SCRATCH } + return + end # Anyone can view projects not owned by a user or a school. can :show, Project, user_id: nil, school_id: nil From 2086c9168dd0e97b75c94be1257a4c7155e008ae Mon Sep 17 00:00:00 2001 From: Nathan Richards Date: Wed, 19 Aug 2026 18:13:47 +0200 Subject: [PATCH 2/3] test: cover student preview permission with persisted role --- spec/models/ability_spec.rb | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb index c648e19aa..ae5052e2e 100644 --- a/spec/models/ability_spec.rb +++ b/spec/models/ability_spec.rb @@ -161,6 +161,34 @@ end end + context 'with a persisted school student' do + let(:school) { create(:school) } + let(:user) { create(:user, id: user_id) } + let(:public_scratch_starter) do + build(:project, + user_id: nil, + school_id: nil, + project_type: Project::Types::CODE_EDITOR_SCRATCH) + end + let(:public_python_starter) do + build(:project, + user_id: nil, + school_id: nil, + project_type: Project::Types::PYTHON) + end + let(:public_scratch_component) { build(:component, project: public_scratch_starter) } + let(:public_python_component) { build(:component, project: public_python_starter) } + + before do + create(:student_role, user_id: user.id, school:) + end + + it { is_expected.to be_able_to(:show, public_scratch_starter) } + it { is_expected.not_to be_able_to(:show, public_python_starter) } + it { is_expected.to be_able_to(:show, public_scratch_component) } + it { is_expected.not_to be_able_to(:show, public_python_component) } + end + context 'with an experience-cs admin' do let(:user) { build(:experience_cs_admin_user, id: user_id) } let(:another_project) { build(:project) } From 49ab7fe7ae9e28540dc80d13f7f2d0a50e4908bf Mon Sep 17 00:00:00 2001 From: Nathan Richards Date: Tue, 25 Aug 2026 16:51:46 +0200 Subject: [PATCH 3/3] fix: apply common unowned-project show rules to students --- app/models/ability.rb | 11 ++--------- spec/models/ability_spec.rb | 30 +----------------------------- 2 files changed, 3 insertions(+), 38 deletions(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index 6627f90b5..cd10e3cac 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -4,7 +4,7 @@ class Ability include CanCan::Ability def initialize(user) - define_common_non_student_abilities(user) + define_common_abilities return unless user @@ -22,14 +22,7 @@ def initialize(user) private - def define_common_non_student_abilities(user) - if user&.student? - # Allow students to view the Experience CS preview starter template. - can :show, Project, user_id: nil, school_id: nil, project_type: Project::Types::CODE_EDITOR_SCRATCH - can :show, Component, project: { user_id: nil, school_id: nil, project_type: Project::Types::CODE_EDITOR_SCRATCH } - return - end - + def define_common_abilities # Anyone can view projects not owned by a user or a school. can :show, Project, user_id: nil, school_id: nil can :show, Component, project: { user_id: nil, school_id: nil } diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb index ae5052e2e..5f34facff 100644 --- a/spec/models/ability_spec.rb +++ b/spec/models/ability_spec.rb @@ -161,34 +161,6 @@ end end - context 'with a persisted school student' do - let(:school) { create(:school) } - let(:user) { create(:user, id: user_id) } - let(:public_scratch_starter) do - build(:project, - user_id: nil, - school_id: nil, - project_type: Project::Types::CODE_EDITOR_SCRATCH) - end - let(:public_python_starter) do - build(:project, - user_id: nil, - school_id: nil, - project_type: Project::Types::PYTHON) - end - let(:public_scratch_component) { build(:component, project: public_scratch_starter) } - let(:public_python_component) { build(:component, project: public_python_starter) } - - before do - create(:student_role, user_id: user.id, school:) - end - - it { is_expected.to be_able_to(:show, public_scratch_starter) } - it { is_expected.not_to be_able_to(:show, public_python_starter) } - it { is_expected.to be_able_to(:show, public_scratch_component) } - it { is_expected.not_to be_able_to(:show, public_python_component) } - end - context 'with an experience-cs admin' do let(:user) { build(:experience_cs_admin_user, id: user_id) } let(:another_project) { build(:project) } @@ -572,7 +544,7 @@ context 'with a starter project' do it { is_expected.not_to be_able_to(:index, starter_project) } - it { is_expected.not_to be_able_to(:show, starter_project) } + it { is_expected.to be_able_to(:show, starter_project) } it { is_expected.not_to be_able_to(:create, starter_project) } it { is_expected.not_to be_able_to(:update, starter_project) } it { is_expected.not_to be_able_to(:destroy, starter_project) }