Skip to content

Commit

Permalink
feat: Allow researchers to get classes and offerings [PT-187212565]
Browse files Browse the repository at this point in the history
- Adds check for researcher in class controllers
- Adds check for researcher in offering policy
- Adds is_researcher_for_clazz? method to users. This is an optimized query that doesn't generate any model objects.

NOTE: this also anonymizes the class student data since reseachers now have access to the class roster.  It also
anonymizes the class student data when the class is retrieved by classword which was a previously bug.
  • Loading branch information
dougmartin committed Mar 25, 2024
1 parent cf77bd9 commit 608e89e
Show file tree
Hide file tree
Showing 4 changed files with 44 additions and 9 deletions.
16 changes: 16 additions & 0 deletions rails/app/controllers/api/api_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -169,4 +169,20 @@ def auth_student_or_teacher(params)

return auth
end

def auth_student_or_teacher_or_researcher(params)
auth = auth_not_anonymous(params)
return auth if auth[:error]
user = auth[:user]

# Check if the user is a researcher of ANY project - the controller will check for a specific resource
auth[:role] ||= {}
auth[:role][:is_project_researcher] = user && user.is_project_researcher?

if !user.portal_student && !user.portal_teacher && !auth[:role][:is_project_researcher]
auth[:error] = 'You must be logged in as a student or teacher or researcher to use this endpoint'
end

return auth
end
end
19 changes: 11 additions & 8 deletions rails/app/controllers/api/v1/classes_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,26 @@ class API::V1::ClassesController < API::APIController

# GET api/v1/classes/:id
def show
auth = auth_student_or_teacher(params)
auth = auth_student_or_teacher_or_researcher(params)
return error(auth[:error]) if auth[:error]
user = auth[:user]
role = auth[:role]

clazz = Portal::Clazz.find_by_id(params[:id])
if !clazz
return error('The requested class was not found')
end

# NOTE: these checks are set so only one of these can be true and researcher access is checked before teacher access
student_in_class = user.portal_student && user.portal_student.has_clazz?(clazz)
teacher_in_class = !student_in_class || (user.portal_teacher && user.portal_teacher.has_clazz?(clazz))
researcher_in_class = !teacher_in_class || (role[:is_project_researcher] && user.is_researcher_for_clazz?(clazz))

if (!student_in_class && !teacher_in_class)
return error('You are not a student or teacher of the requested class')
if (!student_in_class && !teacher_in_class && !researcher_in_class)
return error('You are not a student or teacher or researcher of the requested class')
end

render_info clazz
render_info(clazz, researcher_in_class)
end

# GET api/v1/classes/mine
Expand Down Expand Up @@ -49,7 +52,7 @@ def info
return error('The requested class was not found')
end

render_info clazz
render_info(clazz, true)
end

def log_links
Expand Down Expand Up @@ -100,7 +103,7 @@ def set_is_archived

private

def render_info(clazz)
def render_info(clazz, anonymize)
state = nil
if school = clazz.school
state = school.state
Expand Down Expand Up @@ -128,8 +131,8 @@ def render_info(clazz)
:id => url_for(student.user),
:user_id => student.user.id,
:email => student.user.email,
:first_name => student.user.first_name,
:last_name => student.user.last_name
:first_name => anonymize ? "Student" : student.user.first_name,
:last_name => anonymize ? "#{student.id}" : student.user.last_name
}
},
:offerings => clazz.teacher_visible_offerings.map { |offering|
Expand Down
12 changes: 12 additions & 0 deletions rails/app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -424,6 +424,18 @@ def is_project_member?(project=nil)
is_project_admin?(project) || is_project_researcher?(project) || is_project_cohort_member?(project)
end

def is_researcher_for_clazz?(clazz)
# check if class has teacher in a cohort of a project the user is a researcher of using a explicit join to avoid a
# bunch of unneeded object instantiation
researcher_for_projects
.joins("INNER JOIN admin_cohorts __ac ON __ac.project_id = admin_projects.id")
.joins("INNER JOIN admin_cohort_items __aci ON __aci.admin_cohort_id = __ac.id AND __aci.item_type = 'Portal::Teacher'")
.joins("INNER JOIN portal_teachers __pt ON __pt.id = __aci.item_id")
.joins("INNER JOIN portal_teacher_clazzes __ptc ON __ptc.teacher_id = __pt.id")
.where("__ptc.clazz_id = ?", clazz.id)
.count > 0
end

def add_role_for_project(role, project)
role_attribute = "is_#{role}"
project_user = project_users.find_by_project_id project.id
Expand Down
6 changes: 5 additions & 1 deletion rails/app/policies/portal/offering_policy.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
class Portal::OfferingPolicy < ApplicationPolicy
# Used by API::V1::OfferingsController:
def api_show?
class_teacher_or_admin? || class_student?
class_teacher_or_admin? || class_student? || class_researcher?
end

def api_index?
Expand Down Expand Up @@ -103,4 +103,8 @@ def class_student?
def class_teacher_or_admin?
class_teacher? || admin?
end

def class_researcher?
user && record && user.is_researcher_for_clazz?(record.clazz)
end
end

0 comments on commit 608e89e

Please sign in to comment.