已完善实验管理权限:
校级管理员可查看全部实验项目与成绩单。 学院管理员仅可查看本学院开课,或由本学院教师承担/管理的实验课。 场次取消、参与学生查看也同步执行上述范围校验,避免通过场次 ID 越权。 增加覆盖:校级全量、学院自有课程、跨学院管理课程可见、无关学院课程不可见。
This commit is contained in:
1 parent
1e45ede5be
commit
5872c06042
3 files changed
+97
-9
No files matched your search
@@ -1141,8 +1141,13 @@ public sealed class ExperimentGradesController(
|
|||||||
var scope = currentUserDataScope.Current;
|
var scope = currentUserDataScope.Current;
|
||||||
if (scope.Scope == DataScope.All) return source;
|
if (scope.Scope == DataScope.All) return source;
|
||||||
if (scope.Scope == DataScope.College)
|
if (scope.Scope == DataScope.College)
|
||||||
|
{
|
||||||
|
if (!scope.CollegeId.HasValue) return source.Where(_ => false);
|
||||||
|
var collegeId = scope.CollegeId.Value;
|
||||||
return source.Where(x =>
|
return source.Where(x =>
|
||||||
x.Course!.CollegeId == scope.RestrictedCollegeId);
|
x.Course!.CollegeId == collegeId ||
|
||||||
|
x.Teachers.Any(item => item.Teacher!.CollegeId == collegeId));
|
||||||
|
}
|
||||||
if (scope.IsInRole(SystemRoles.Teacher))
|
if (scope.IsInRole(SystemRoles.Teacher))
|
||||||
return source.Where(x => x.Teachers.Any(item =>
|
return source.Where(x => x.Teachers.Any(item =>
|
||||||
item.Teacher!.UserId == scope.UserId));
|
item.Teacher!.UserId == scope.UserId));
|
||||||
|
|||||||
@@ -1058,6 +1058,10 @@ public sealed class ExperimentsController(
|
|||||||
.Include(x => x.ExperimentProject)
|
.Include(x => x.ExperimentProject)
|
||||||
.ThenInclude(x => x!.TeachingTask)
|
.ThenInclude(x => x!.TeachingTask)
|
||||||
.ThenInclude(x => x!.Course)
|
.ThenInclude(x => x!.Course)
|
||||||
|
.Include(x => x.ExperimentProject)
|
||||||
|
.ThenInclude(x => x!.TeachingTask)
|
||||||
|
.ThenInclude(x => x!.Teachers)
|
||||||
|
.ThenInclude(x => x.Teacher)
|
||||||
.Include(x => x.Instructors)
|
.Include(x => x.Instructors)
|
||||||
.ThenInclude(x => x.Teacher)
|
.ThenInclude(x => x.Teacher)
|
||||||
.FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
|
.FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
|
||||||
@@ -1124,6 +1128,12 @@ public sealed class ExperimentsController(
|
|||||||
{
|
{
|
||||||
var session = await db.ExperimentSessions.AsNoTracking()
|
var session = await db.ExperimentSessions.AsNoTracking()
|
||||||
.Include(x => x.ExperimentProject)
|
.Include(x => x.ExperimentProject)
|
||||||
|
.ThenInclude(x => x!.TeachingTask)
|
||||||
|
.ThenInclude(x => x!.Course)
|
||||||
|
.Include(x => x.ExperimentProject)
|
||||||
|
.ThenInclude(x => x!.TeachingTask)
|
||||||
|
.ThenInclude(x => x!.Teachers)
|
||||||
|
.ThenInclude(x => x.Teacher)
|
||||||
.Include(x => x.Instructors)
|
.Include(x => x.Instructors)
|
||||||
.ThenInclude(x => x.Teacher)
|
.ThenInclude(x => x.Teacher)
|
||||||
.FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
|
.FirstOrDefaultAsync(x => x.Id == id, cancellationToken);
|
||||||
@@ -1541,8 +1551,13 @@ public sealed class ExperimentsController(
|
|||||||
var scope = currentUserDataScope.Current;
|
var scope = currentUserDataScope.Current;
|
||||||
if (scope.Scope == DataScope.All) return source;
|
if (scope.Scope == DataScope.All) return source;
|
||||||
if (scope.Scope == DataScope.College)
|
if (scope.Scope == DataScope.College)
|
||||||
|
{
|
||||||
|
if (!scope.CollegeId.HasValue) return source.Where(_ => false);
|
||||||
|
var collegeId = scope.CollegeId.Value;
|
||||||
return source.Where(x =>
|
return source.Where(x =>
|
||||||
x.Course!.CollegeId == scope.RestrictedCollegeId);
|
x.Course!.CollegeId == collegeId ||
|
||||||
|
x.Teachers.Any(item => item.Teacher!.CollegeId == collegeId));
|
||||||
|
}
|
||||||
if (scope.IsInRole(SystemRoles.Teacher))
|
if (scope.IsInRole(SystemRoles.Teacher))
|
||||||
return source.Where(x =>
|
return source.Where(x =>
|
||||||
x.Teachers.Any(item =>
|
x.Teachers.Any(item =>
|
||||||
@@ -1560,8 +1575,15 @@ public sealed class ExperimentsController(
|
|||||||
private bool CanManageSession(ExperimentSession session)
|
private bool CanManageSession(ExperimentSession session)
|
||||||
{
|
{
|
||||||
var scope = currentUserDataScope.Current;
|
var scope = currentUserDataScope.Current;
|
||||||
if (scope.Scope is DataScope.All or DataScope.College)
|
if (scope.Scope == DataScope.All) return true;
|
||||||
return true;
|
if (scope.Scope == DataScope.College && scope.CollegeId.HasValue)
|
||||||
|
{
|
||||||
|
var collegeId = scope.CollegeId.Value;
|
||||||
|
var task = session.ExperimentProject?.TeachingTask;
|
||||||
|
return task?.Course?.CollegeId == collegeId ||
|
||||||
|
task?.Teachers.Any(item =>
|
||||||
|
item.Teacher?.CollegeId == collegeId) == true;
|
||||||
|
}
|
||||||
return scope.IsInRole(SystemRoles.Teacher) &&
|
return scope.IsInRole(SystemRoles.Teacher) &&
|
||||||
session.Instructors.Any(instructor =>
|
session.Instructors.Any(instructor =>
|
||||||
instructor.Teacher?.UserId == scope.UserId);
|
instructor.Teacher?.UserId == scope.UserId);
|
||||||
|
|||||||
@@ -179,10 +179,65 @@ public sealed class ExperimentGradesControllerTests
|
|||||||
Status = ExperimentProjectStatus.Published,
|
Status = ExperimentProjectStatus.Published,
|
||||||
PublishedAt = DateTime.UtcNow
|
PublishedAt = DateTime.UtcNow
|
||||||
};
|
};
|
||||||
fixture.Db.AddRange(otherCollege, otherCourse, otherTask, otherProject);
|
var managedCourse = new Course
|
||||||
|
{
|
||||||
|
CollegeId = otherCollege.Id,
|
||||||
|
Code = "SHARED-LAB",
|
||||||
|
Name = "跨学院管理实验",
|
||||||
|
Credits = 1,
|
||||||
|
TotalHours = 16,
|
||||||
|
PracticeHours = 16,
|
||||||
|
Nature = CourseNature.Practice,
|
||||||
|
AssessmentMethod = AssessmentMethod.Assessment
|
||||||
|
};
|
||||||
|
var managedTask = new TeachingTask
|
||||||
|
{
|
||||||
|
AcademicTermId = fixture.Term.Id,
|
||||||
|
CourseId = managedCourse.Id,
|
||||||
|
TaskNumber = "SHARED-LAB-01",
|
||||||
|
Name = "跨学院管理实验班",
|
||||||
|
Capacity = 20,
|
||||||
|
Status = TeachingTaskStatus.Published,
|
||||||
|
Teachers =
|
||||||
|
[
|
||||||
|
new TeachingTaskTeacher
|
||||||
|
{
|
||||||
|
TeacherId = await fixture.Db.Teachers
|
||||||
|
.Where(x => x.UserId == fixture.TeacherScope.Current.UserId)
|
||||||
|
.Select(x => x.Id)
|
||||||
|
.SingleAsync(),
|
||||||
|
IsPrimary = true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
var managedProject = new ExperimentProject
|
||||||
|
{
|
||||||
|
TeachingTaskId = managedTask.Id,
|
||||||
|
Code = "SHARED-LAB",
|
||||||
|
Name = "可见的跨学院实验",
|
||||||
|
ArrangementMode = ExperimentArrangementMode.Centralized,
|
||||||
|
StartDate = fixture.Term.StartDate,
|
||||||
|
EndDate = fixture.Term.EndDate,
|
||||||
|
Status = ExperimentProjectStatus.Published,
|
||||||
|
PublishedAt = DateTime.UtcNow
|
||||||
|
};
|
||||||
|
fixture.Db.AddRange(
|
||||||
|
otherCollege, otherCourse, otherTask, otherProject,
|
||||||
|
managedCourse, managedTask, managedProject);
|
||||||
await fixture.Db.SaveChangesAsync();
|
await fixture.Db.SaveChangesAsync();
|
||||||
fixture.Db.ChangeTracker.Clear();
|
fixture.Db.ChangeTracker.Clear();
|
||||||
|
|
||||||
|
var schoolScope = new FixedScope(new CurrentUserScope(
|
||||||
|
Guid.NewGuid(),
|
||||||
|
"校级管理员",
|
||||||
|
null,
|
||||||
|
DataScope.All,
|
||||||
|
new HashSet<string>([SystemRoles.AcademicAdmin])));
|
||||||
|
var schoolPage = Assert.IsType<OkObjectResult>(await fixture
|
||||||
|
.ExperimentGrades(schoolScope)
|
||||||
|
.GetManagement(null, null, null, null, 1, 10, CancellationToken.None));
|
||||||
|
Assert.Equal(3, Property<int>(schoolPage.Value, "Total"));
|
||||||
|
|
||||||
var collegeScope = new FixedScope(new CurrentUserScope(
|
var collegeScope = new FixedScope(new CurrentUserScope(
|
||||||
Guid.NewGuid(),
|
Guid.NewGuid(),
|
||||||
"学院管理员",
|
"学院管理员",
|
||||||
@@ -198,11 +253,17 @@ public sealed class ExperimentGradesControllerTests
|
|||||||
1,
|
1,
|
||||||
10,
|
10,
|
||||||
CancellationToken.None));
|
CancellationToken.None));
|
||||||
Assert.Equal(1, Property<int>(page.Value, "Total"));
|
Assert.Equal(2, Property<int>(page.Value, "Total"));
|
||||||
var course = Assert.Single(Property<System.Collections.IEnumerable>(
|
var courses = Property<System.Collections.IEnumerable>(page.Value, "Items")
|
||||||
page.Value, "Items").Cast<object>());
|
.Cast<object>()
|
||||||
|
.ToList();
|
||||||
|
Assert.Equal(2, courses.Count);
|
||||||
|
Assert.Contains(courses, course => Property<string>(course, "CourseCode") == "SHARED-LAB");
|
||||||
|
Assert.DoesNotContain(courses, course => Property<string>(course, "CourseCode") == "OTHER-LAB");
|
||||||
|
var ownCourse = Assert.Single(courses, course =>
|
||||||
|
Property<string>(course, "CourseCode") == "CSLAB");
|
||||||
Assert.Equal(12, Property<System.Collections.IEnumerable>(
|
Assert.Equal(12, Property<System.Collections.IEnumerable>(
|
||||||
course, "Projects").Cast<object>().Count());
|
ownCourse, "Projects").Cast<object>().Count());
|
||||||
|
|
||||||
var filtered = Assert.IsType<OkObjectResult>(await controller.GetManagement(
|
var filtered = Assert.IsType<OkObjectResult>(await controller.GetManagement(
|
||||||
null,
|
null,
|
||||||
|
|||||||
Reference in new issue
Block a user