CourseSecurity.java

1
package edu.ucsb.cs156.frontiers.config;
2
3
import edu.ucsb.cs156.frontiers.entities.Course;
4
import edu.ucsb.cs156.frontiers.models.CurrentUser;
5
import edu.ucsb.cs156.frontiers.repositories.CourseRepository;
6
import edu.ucsb.cs156.frontiers.repositories.RosterStudentRepository;
7
import edu.ucsb.cs156.frontiers.services.CurrentUserService;
8
import java.util.Collection;
9
import java.util.Optional;
10
import lombok.extern.slf4j.Slf4j;
11
import org.springframework.security.access.expression.method.MethodSecurityExpressionOperations;
12
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
13
import org.springframework.security.access.prepost.PreAuthorize;
14
import org.springframework.security.core.GrantedAuthority;
15
import org.springframework.stereotype.Component;
16
17
/**
18
 * CourseSecurity provides methods to check permissions for managing courses and roster students. It
19
 * uses the CurrentUserService to get the current user and RoleHierarchy to check roles.
20
 *
21
 * <p>The methods defined here are used as annotations (e.g. <code>
22
 *   @PreAuthorize("@CourseSecurity.hasManagePermissions(#root, #id)")</code>) in the
23
 * CourseController and RosterStudentController to enforce security checks.
24
 *
25
 * <p>Note that for a method with a courseId, you <em>still</em> need to verify in each method,
26
 * whether the course exists or not. These annotations will <em>only</em> check whether or not the
27
 * particular user has access to a particular course, for example.
28
 *
29
 * <p>When testing, use <code>@WithInstructorCoursePermissions</code> or <code>
30
 * @WithStaffCoursePermissions</code> to mock a user with the appropriate roles.
31
 */
32
@Slf4j
33
@Component("CourseSecurity")
34
public class CourseSecurity {
35
  private final CurrentUserService currentUserService;
36
  private final RoleHierarchy roleHierarchy;
37
  private final CourseRepository courseRepository;
38
  private final RosterStudentRepository rosterStudentRepository;
39
40
  public CourseSecurity(
41
      CurrentUserService currentUserService,
42
      RoleHierarchy roleHierarchy,
43
      CourseRepository courseRepository,
44
      RosterStudentRepository rosterStudentRepository) {
45
    this.currentUserService = currentUserService;
46
    this.roleHierarchy = roleHierarchy;
47
    this.courseRepository = courseRepository;
48
    this.rosterStudentRepository = rosterStudentRepository;
49
  }
50
51
  /**
52
   * Use this when you want to check whether the user is either a staff member, instructor or admin
53
   * for the course.
54
   *
55
   * @param operations
56
   * @param courseId
57
   * @return true if the user has manage permissions for the course, false otherwise.
58
   */
59
  @PreAuthorize("hasRole('ROLE_USER')")
60
  public Boolean hasManagePermissions(
61
      MethodSecurityExpressionOperations operations, Long courseId) {
62
    Optional<Course> course = courseRepository.findById(courseId);
63 1 1. hasManagePermissions : negated conditional → KILLED
    if (course.isEmpty()) {
64 1 1. hasManagePermissions : replaced Boolean return with False for edu/ucsb/cs156/frontiers/config/CourseSecurity::hasManagePermissions → KILLED
      return true;
65
    }
66 2 1. hasManagePermissions : replaced Boolean return with True for edu/ucsb/cs156/frontiers/config/CourseSecurity::hasManagePermissions → KILLED
2. hasManagePermissions : replaced Boolean return with False for edu/ucsb/cs156/frontiers/config/CourseSecurity::hasManagePermissions → KILLED
    return baseHasManagePermissions(operations, course.get());
67
  }
68
69
  /**
70
   * Use this for operations that only an instructor can do, but not a staff member, such as adding
71
   * or deleting a course staff member.
72
   *
73
   * @param operations
74
   * @param courseId
75
   * @return true if the user has instructor permissions for the course, false otherwise.
76
   */
77
  @PreAuthorize("hasRole('ROLE_INSTRUCTOR')")
78
  public Boolean hasInstructorPermissions(
79
      MethodSecurityExpressionOperations operations, Long courseId) {
80
    CurrentUser currentUser = currentUserService.getCurrentUser();
81
    Collection<? extends GrantedAuthority> authorities =
82
        roleHierarchy.getReachableGrantedAuthorities(currentUser.getRoles());
83 3 1. hasInstructorPermissions : negated conditional → KILLED
2. lambda$hasInstructorPermissions$0 : replaced boolean return with false for edu/ucsb/cs156/frontiers/config/CourseSecurity::lambda$hasInstructorPermissions$0 → KILLED
3. lambda$hasInstructorPermissions$0 : replaced boolean return with true for edu/ucsb/cs156/frontiers/config/CourseSecurity::lambda$hasInstructorPermissions$0 → KILLED
    if (authorities.stream().anyMatch(role -> role.getAuthority().equals("ROLE_ADMIN"))) {
84 1 1. hasInstructorPermissions : replaced Boolean return with False for edu/ucsb/cs156/frontiers/config/CourseSecurity::hasInstructorPermissions → KILLED
      return true;
85
    } else {
86
      Optional<Course> course = courseRepository.findById(courseId);
87 1 1. hasInstructorPermissions : negated conditional → KILLED
      if (course.isEmpty()) {
88 1 1. hasInstructorPermissions : replaced Boolean return with False for edu/ucsb/cs156/frontiers/config/CourseSecurity::hasInstructorPermissions → KILLED
        return true;
89
      }
90 2 1. hasInstructorPermissions : replaced Boolean return with True for edu/ucsb/cs156/frontiers/config/CourseSecurity::hasInstructorPermissions → KILLED
2. hasInstructorPermissions : replaced Boolean return with False for edu/ucsb/cs156/frontiers/config/CourseSecurity::hasInstructorPermissions → KILLED
      return currentUser.getUser().getEmail().equals(course.get().getInstructorEmail());
91
    }
92
  }
93
94
  /**
95
   * This method checks if the current user has management permissions for the course associated
96
   * with the given rosterStudent. This allows us to create endpoints that just take a roster
97
   * student id, not a course id, and still check permissions. This one works for both staff and
98
   * instructor permissions.
99
   *
100
   * @param operations
101
   * @param rosterStudentId
102
   * @return
103
   */
104
  @PreAuthorize("hasRole('ROLE_USER')")
105
  public Boolean hasRosterStudentManagementPermissions(
106
      MethodSecurityExpressionOperations operations, Long rosterStudentId) {
107 2 1. hasRosterStudentManagementPermissions : replaced Boolean return with True for edu/ucsb/cs156/frontiers/config/CourseSecurity::hasRosterStudentManagementPermissions → KILLED
2. hasRosterStudentManagementPermissions : replaced Boolean return with False for edu/ucsb/cs156/frontiers/config/CourseSecurity::hasRosterStudentManagementPermissions → KILLED
    return rosterStudentRepository
108
        .findById(rosterStudentId)
109 2 1. lambda$hasRosterStudentManagementPermissions$1 : replaced Boolean return with True for edu/ucsb/cs156/frontiers/config/CourseSecurity::lambda$hasRosterStudentManagementPermissions$1 → KILLED
2. lambda$hasRosterStudentManagementPermissions$1 : replaced Boolean return with False for edu/ucsb/cs156/frontiers/config/CourseSecurity::lambda$hasRosterStudentManagementPermissions$1 → KILLED
        .map(rosterStudent -> baseHasManagePermissions(operations, rosterStudent.getCourse()))
110
        .orElse(true);
111
  }
112
113
  /**
114
   * This is a helper method that checks if the current user has management permissions for the
115
   * given course.
116
   *
117
   * @param operations
118
   * @param course
119
   * @return
120
   */
121
  public Boolean baseHasManagePermissions(
122
      MethodSecurityExpressionOperations operations, Course course) {
123
    CurrentUser currentUser = currentUserService.getCurrentUser();
124
    Collection<? extends GrantedAuthority> authorities =
125
        roleHierarchy.getReachableGrantedAuthorities(currentUser.getRoles());
126 3 1. baseHasManagePermissions : negated conditional → KILLED
2. lambda$baseHasManagePermissions$2 : replaced boolean return with true for edu/ucsb/cs156/frontiers/config/CourseSecurity::lambda$baseHasManagePermissions$2 → KILLED
3. lambda$baseHasManagePermissions$2 : replaced boolean return with false for edu/ucsb/cs156/frontiers/config/CourseSecurity::lambda$baseHasManagePermissions$2 → KILLED
    if (authorities.stream().anyMatch(role -> role.getAuthority().equals("ROLE_ADMIN"))) {
127 1 1. baseHasManagePermissions : replaced Boolean return with False for edu/ucsb/cs156/frontiers/config/CourseSecurity::baseHasManagePermissions → KILLED
      return true;
128
    } else {
129
      if (course.getCourseStaff().stream()
130 3 1. baseHasManagePermissions : negated conditional → KILLED
2. lambda$baseHasManagePermissions$3 : replaced boolean return with false for edu/ucsb/cs156/frontiers/config/CourseSecurity::lambda$baseHasManagePermissions$3 → KILLED
3. lambda$baseHasManagePermissions$3 : replaced boolean return with true for edu/ucsb/cs156/frontiers/config/CourseSecurity::lambda$baseHasManagePermissions$3 → KILLED
          .anyMatch(staff -> staff.getEmail().equals(currentUser.getUser().getEmail()))) {
131 1 1. baseHasManagePermissions : replaced Boolean return with False for edu/ucsb/cs156/frontiers/config/CourseSecurity::baseHasManagePermissions → KILLED
        return true;
132
      }
133 2 1. baseHasManagePermissions : replaced Boolean return with False for edu/ucsb/cs156/frontiers/config/CourseSecurity::baseHasManagePermissions → KILLED
2. baseHasManagePermissions : replaced Boolean return with True for edu/ucsb/cs156/frontiers/config/CourseSecurity::baseHasManagePermissions → KILLED
      return currentUser.getUser().getEmail().equals(course.getInstructorEmail());
134
    }
135
  }
136
}

Mutations

63

1.1
Location : hasManagePermissions
Killed by : edu.ucsb.cs156.frontiers.security.CourseSecurityTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.security.CourseSecurityTests]/[nested-class:NotFound]/[method:null_on_null()]
negated conditional → KILLED

64

1.1
Location : hasManagePermissions
Killed by : edu.ucsb.cs156.frontiers.security.CourseSecurityTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.security.CourseSecurityTests]/[nested-class:NotFound]/[method:null_on_null()]
replaced Boolean return with False for edu/ucsb/cs156/frontiers/config/CourseSecurity::hasManagePermissions → KILLED

66

1.1
Location : hasManagePermissions
Killed by : edu.ucsb.cs156.frontiers.security.CourseSecurityTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.security.CourseSecurityTests]/[nested-class:UnsuccessfulInstructor]/[method:instructor_cant_load_non_owned_course()]
replaced Boolean return with True for edu/ucsb/cs156/frontiers/config/CourseSecurity::hasManagePermissions → KILLED

2.2
Location : hasManagePermissions
Killed by : edu.ucsb.cs156.frontiers.security.CourseSecurityTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.security.CourseSecurityTests]/[nested-class:SuccessfulAdmin]/[method:instructor_can_load_owned_course()]
replaced Boolean return with False for edu/ucsb/cs156/frontiers/config/CourseSecurity::hasManagePermissions → KILLED

83

1.1
Location : hasInstructorPermissions
Killed by : edu.ucsb.cs156.frontiers.security.CourseSecurityTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.security.CourseSecurityTests]/[nested-class:SuccessfulAdminPerms]/[method:instructor_can_load_owned_course()]
negated conditional → KILLED

2.2
Location : lambda$hasInstructorPermissions$0
Killed by : edu.ucsb.cs156.frontiers.security.CourseSecurityTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.security.CourseSecurityTests]/[nested-class:SuccessfulAdminPerms]/[method:instructor_can_load_owned_course()]
replaced boolean return with false for edu/ucsb/cs156/frontiers/config/CourseSecurity::lambda$hasInstructorPermissions$0 → KILLED

3.3
Location : lambda$hasInstructorPermissions$0
Killed by : edu.ucsb.cs156.frontiers.security.CourseSecurityTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.security.CourseSecurityTests]/[nested-class:UnsuccessfulInstructorPerms]/[method:instructor_cant_load_non_owned_course()]
replaced boolean return with true for edu/ucsb/cs156/frontiers/config/CourseSecurity::lambda$hasInstructorPermissions$0 → KILLED

84

1.1
Location : hasInstructorPermissions
Killed by : edu.ucsb.cs156.frontiers.security.CourseSecurityTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.security.CourseSecurityTests]/[nested-class:SuccessfulAdminPerms]/[method:instructor_can_load_owned_course()]
replaced Boolean return with False for edu/ucsb/cs156/frontiers/config/CourseSecurity::hasInstructorPermissions → KILLED

87

1.1
Location : hasInstructorPermissions
Killed by : edu.ucsb.cs156.frontiers.security.CourseSecurityTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.security.CourseSecurityTests]/[nested-class:NotFoundInstructor]/[method:null_on_null()]
negated conditional → KILLED

88

1.1
Location : hasInstructorPermissions
Killed by : edu.ucsb.cs156.frontiers.security.CourseSecurityTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.security.CourseSecurityTests]/[nested-class:NotFoundInstructor]/[method:null_on_null()]
replaced Boolean return with False for edu/ucsb/cs156/frontiers/config/CourseSecurity::hasInstructorPermissions → KILLED

90

1.1
Location : hasInstructorPermissions
Killed by : edu.ucsb.cs156.frontiers.security.CourseSecurityTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.security.CourseSecurityTests]/[nested-class:UnsuccessfulInstructorPerms]/[method:instructor_cant_load_non_owned_course()]
replaced Boolean return with True for edu/ucsb/cs156/frontiers/config/CourseSecurity::hasInstructorPermissions → KILLED

2.2
Location : hasInstructorPermissions
Killed by : edu.ucsb.cs156.frontiers.security.CourseSecurityTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.security.CourseSecurityTests]/[nested-class:SuccessfulInstructorPerms]/[method:instructor_can_load_owned_course()]
replaced Boolean return with False for edu/ucsb/cs156/frontiers/config/CourseSecurity::hasInstructorPermissions → KILLED

107

1.1
Location : hasRosterStudentManagementPermissions
Killed by : edu.ucsb.cs156.frontiers.security.CourseSecurityTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.security.CourseSecurityTests]/[nested-class:CorrectPassRosterStudent]/[method:returns_properly()]
replaced Boolean return with True for edu/ucsb/cs156/frontiers/config/CourseSecurity::hasRosterStudentManagementPermissions → KILLED

2.2
Location : hasRosterStudentManagementPermissions
Killed by : edu.ucsb.cs156.frontiers.security.CourseSecurityTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.security.CourseSecurityTests]/[nested-class:NotFoundRosterStudent]/[method:null_on_null()]
replaced Boolean return with False for edu/ucsb/cs156/frontiers/config/CourseSecurity::hasRosterStudentManagementPermissions → KILLED

109

1.1
Location : lambda$hasRosterStudentManagementPermissions$1
Killed by : edu.ucsb.cs156.frontiers.security.CourseSecurityTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.security.CourseSecurityTests]/[nested-class:CorrectPassRosterStudent]/[method:returns_properly()]
replaced Boolean return with True for edu/ucsb/cs156/frontiers/config/CourseSecurity::lambda$hasRosterStudentManagementPermissions$1 → KILLED

2.2
Location : lambda$hasRosterStudentManagementPermissions$1
Killed by : edu.ucsb.cs156.frontiers.security.CourseSecurityTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.security.CourseSecurityTests]/[nested-class:FalseButExistsRosterStudent]/[method:returns_properly()]
replaced Boolean return with False for edu/ucsb/cs156/frontiers/config/CourseSecurity::lambda$hasRosterStudentManagementPermissions$1 → KILLED

126

1.1
Location : baseHasManagePermissions
Killed by : edu.ucsb.cs156.frontiers.security.CourseSecurityTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.security.CourseSecurityTests]/[nested-class:SuccessfulAdmin]/[method:instructor_can_load_owned_course()]
negated conditional → KILLED

2.2
Location : lambda$baseHasManagePermissions$2
Killed by : edu.ucsb.cs156.frontiers.security.CourseSecurityTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.security.CourseSecurityTests]/[nested-class:CorrectPassRosterStudent]/[method:returns_properly()]
replaced boolean return with true for edu/ucsb/cs156/frontiers/config/CourseSecurity::lambda$baseHasManagePermissions$2 → KILLED

3.3
Location : lambda$baseHasManagePermissions$2
Killed by : edu.ucsb.cs156.frontiers.security.CourseSecurityTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.security.CourseSecurityTests]/[nested-class:SuccessfulAdmin]/[method:instructor_can_load_owned_course()]
replaced boolean return with false for edu/ucsb/cs156/frontiers/config/CourseSecurity::lambda$baseHasManagePermissions$2 → KILLED

127

1.1
Location : baseHasManagePermissions
Killed by : edu.ucsb.cs156.frontiers.security.CourseSecurityTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.security.CourseSecurityTests]/[nested-class:SuccessfulAdmin]/[method:instructor_can_load_owned_course()]
replaced Boolean return with False for edu/ucsb/cs156/frontiers/config/CourseSecurity::baseHasManagePermissions → KILLED

130

1.1
Location : baseHasManagePermissions
Killed by : edu.ucsb.cs156.frontiers.security.CourseSecurityTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.security.CourseSecurityTests]/[nested-class:SuccessfulCourseStaff]/[method:instructor_can_load_owned_course()]
negated conditional → KILLED

2.2
Location : lambda$baseHasManagePermissions$3
Killed by : edu.ucsb.cs156.frontiers.security.CourseSecurityTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.security.CourseSecurityTests]/[nested-class:SuccessfulCourseStaff]/[method:instructor_can_load_owned_course()]
replaced boolean return with false for edu/ucsb/cs156/frontiers/config/CourseSecurity::lambda$baseHasManagePermissions$3 → KILLED

3.3
Location : lambda$baseHasManagePermissions$3
Killed by : edu.ucsb.cs156.frontiers.security.CourseSecurityTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.security.CourseSecurityTests]/[nested-class:UnsuccessfulInstructor]/[method:instructor_cant_load_non_owned_course()]
replaced boolean return with true for edu/ucsb/cs156/frontiers/config/CourseSecurity::lambda$baseHasManagePermissions$3 → KILLED

131

1.1
Location : baseHasManagePermissions
Killed by : edu.ucsb.cs156.frontiers.security.CourseSecurityTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.security.CourseSecurityTests]/[nested-class:SuccessfulCourseStaff]/[method:instructor_can_load_owned_course()]
replaced Boolean return with False for edu/ucsb/cs156/frontiers/config/CourseSecurity::baseHasManagePermissions → KILLED

133

1.1
Location : baseHasManagePermissions
Killed by : edu.ucsb.cs156.frontiers.security.CourseSecurityTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.security.CourseSecurityTests]/[nested-class:SuccessfulInstructor]/[method:instructor_can_load_owned_course()]
replaced Boolean return with False for edu/ucsb/cs156/frontiers/config/CourseSecurity::baseHasManagePermissions → KILLED

2.2
Location : baseHasManagePermissions
Killed by : edu.ucsb.cs156.frontiers.security.CourseSecurityTests.[engine:junit-jupiter]/[class:edu.ucsb.cs156.frontiers.security.CourseSecurityTests]/[nested-class:CorrectPassRosterStudent]/[method:returns_properly()]
replaced Boolean return with True for edu/ucsb/cs156/frontiers/config/CourseSecurity::baseHasManagePermissions → KILLED

Active mutators

Tests examined


Report generated by PIT 1.17.0