Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions .github/workflows/security-spring.yml
Original file line number Diff line number Diff line change
Expand Up @@ -159,10 +159,21 @@ jobs:
# ──────────────────────────────
# Trivy (filesystem scan) → SARIF
# ──────────────────────────────
- name: Install Trivy
run: |
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key \
| gpg --dearmor \
| sudo tee /etc/apt/trusted.gpg.d/trivy.gpg > /dev/null
echo "deb https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main" \
| sudo tee /etc/apt/sources.list.d/trivy.list
sudo apt-get update -qq && sudo apt-get install -y trivy
trivy --version

- name: Trivy filesystem scan
uses: aquasecurity/trivy-action@0.28.0
uses: aquasecurity/trivy-action@0.34.1
with:
scan-type: fs
skip-setup-trivy: true
ignore-unfixed: true
severity: HIGH,CRITICAL
format: sarif
Expand Down Expand Up @@ -235,9 +246,10 @@ jobs:

- name: Trivy image scan
if: ${{ steps.df.outputs.found == 'true' }}
uses: aquasecurity/trivy-action@0.28.0
uses: aquasecurity/trivy-action@0.34.1
with:
scan-type: image
skip-setup-trivy: true
image-ref: local/lms-back:secscan
ignore-unfixed: true
severity: HIGH,CRITICAL
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.mjsec.lms.controller;

import com.mjsec.lms.domain.Plan;
import com.mjsec.lms.domain.User;
import com.mjsec.lms.dto.*;
import com.mjsec.lms.service.AssignmentSubmissionService;
Expand All @@ -11,7 +10,6 @@
import com.mjsec.lms.util.ValidationUtils;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import org.apache.coyote.Response;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;
Expand Down Expand Up @@ -108,7 +106,7 @@ public ResponseEntity<SuccessResponse<SubmissionResponse>> submitAssignment(
Long currentUserStudentNumber = (Long) authentication.getPrincipal();

validationUtils.validatePlanBelongsToGroup(planId, groupId);
Plan plan = validationUtils.validatePlan(planId);
validationUtils.validatePlan(planId);

//클라이언트 IP 뽑아내기
String clientIpAddr = IpUtils.getClientIp(request);
Expand Down Expand Up @@ -184,7 +182,7 @@ public ResponseEntity<SuccessResponse<DetailSubmissionResponse>> updateSubmissio
Long currentUserStudentNumber = (Long) authentication.getPrincipal();

validationUtils.validateSubmissionFullAccess(groupId, planId, submitId);
Plan plan = validationUtils.validatePlan(planId);
validationUtils.validatePlan(planId);

DetailSubmissionResponse detailSubmissionResponse = assignmentSubmissionService.updateAssignmentSubmission(groupId, planId, submitId, currentUserStudentNumber, dto);

Expand Down
8 changes: 5 additions & 3 deletions src/main/java/com/mjsec/lms/service/MentorService.java
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
package com.mjsec.lms.service;

import com.mjsec.lms.domain.GroupMember;
import com.mjsec.lms.domain.StudyActivity;
import com.mjsec.lms.domain.StudyGroup;
import com.mjsec.lms.domain.User;
import com.mjsec.lms.dto.StudyActivityDto;
import com.mjsec.lms.dto.StudyGroupPutDto;
import com.mjsec.lms.dto.StudyGroupPutResponse;
import com.mjsec.lms.exception.RestApiException;
import com.mjsec.lms.repository.AttendanceRepository;
import com.mjsec.lms.repository.GroupMemberRepository;
import com.mjsec.lms.repository.PlanCommentRepository;
import com.mjsec.lms.repository.PlanRepository;
import com.mjsec.lms.repository.StudyActivityRepository;
import com.mjsec.lms.repository.StudyGroupRepository;
import com.mjsec.lms.repository.SubmissionRepository;
import com.mjsec.lms.repository.UserRepository;
import com.mjsec.lms.type.ErrorCode;
import com.mjsec.lms.type.UserRole;
import java.util.List;
import java.util.Objects;

Expand Down Expand Up @@ -70,6 +68,10 @@ public StudyGroup checkMentor(Long currentStudentNumber, Long groupId) {
StudyGroup studyGroup = studyGroupRepository.findByStudyId(groupId)
.orElseThrow(() -> new RestApiException(ErrorCode.STUDY_NOT_FOUND));

if (user.getRole() == UserRole.ROLE_ADMIN) {
return studyGroup; // 어드민은 creator 체크 없이 통과
}

if(!Objects.equals(user.getUserId(), studyGroup.getCreator().getUserId())) {
throw new RestApiException(ErrorCode.MENTOR_ONLY_CAN_DELETE_MEMBER);
}
Expand Down
47 changes: 25 additions & 22 deletions src/main/java/com/mjsec/lms/util/ValidationUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -112,12 +112,22 @@ public StudyActivity validateStudyActivity(Long activityId) {

// 사용자가 해당 스터디 그룹의 멤버인지 확인
public void validateGroupMembership(User user, StudyGroup studyGroup) {
if (user.getRole() == UserRole.ROLE_ADMIN) {
return; // 어드민은 모든 그룹 접근 가능
}
groupMemberRepository.findByUserAndStudyGroup(user, studyGroup)
.orElseThrow(() -> new RestApiException(ErrorCode.STUDY_USER_NOT_FOUND));
}

// 사용자가 해당 스터디 그룹의 멘토인지 확인
public void validateMentorRole(Long userId, Long groupId) {
User user = userRepository.findById(userId)
.orElseThrow(() -> new RestApiException(ErrorCode.USER_NOT_FOUND));

if (user.getRole() == UserRole.ROLE_ADMIN) {
return; // 어드민은 멘토 권한 보유
}

GroupMemberRole userRole = groupMemberRepository.findRoleByUserIdAndStudyId(userId, groupId)
.orElseThrow(() -> new RestApiException(ErrorCode.STUDY_USER_NOT_FOUND));

Expand Down Expand Up @@ -188,9 +198,9 @@ public void validatePlanBelongsToGroup(Long planId, Long groupId) {
}

// 제출물의 완전한 연관관계 검증
public AssignmentSubmission validateSubmissionFullAccess(Long groupId, Long planId, Long submitId) {
public void validateSubmissionFullAccess(Long groupId, Long planId, Long submitId) {
validatePlanBelongsToGroup(planId, groupId);
return validateSubmissionAccess(planId, submitId);
validateSubmissionAccess(planId, submitId);
}

// 댓글 관리 권한 검증 강화 (작성자 본인 + 멘토 권한)
Expand Down Expand Up @@ -300,27 +310,20 @@ public void validateSubmissionStatusForUpdate(AssignmentSubmission submission) {

// 상태 전환이 유효한지 검증
public void validateStatusTransition(SubmissionStatus currentStatus, SubmissionStatus newStatus) {
boolean isValidTransition = false;

switch (currentStatus) {
case SUBMITTED:
boolean isValidTransition = switch (currentStatus) {
case SUBMITTED ->
// 제출 완료 -> 완료 또는 수정 필요
isValidTransition = (newStatus == SubmissionStatus.COMPLETED ||
newStatus == SubmissionStatus.REVISION_REQUIRED);
break;

case REVISION_REQUIRED:
(newStatus == SubmissionStatus.COMPLETED ||
newStatus == SubmissionStatus.REVISION_REQUIRED);
case REVISION_REQUIRED ->
// 수정 필요 -> 제출 완료 (재제출 시)
isValidTransition = (newStatus == SubmissionStatus.SUBMITTED ||
newStatus == SubmissionStatus.COMPLETED);
break;

case COMPLETED:
(newStatus == SubmissionStatus.SUBMITTED ||
newStatus == SubmissionStatus.COMPLETED);
case COMPLETED ->
// 완료 -> 수정 필요 (피드백 수정 시에만)
isValidTransition = (newStatus == SubmissionStatus.REVISION_REQUIRED ||
newStatus == SubmissionStatus.COMPLETED); // 피드백 수정
break;
}
(newStatus == SubmissionStatus.REVISION_REQUIRED ||
newStatus == SubmissionStatus.COMPLETED); // 피드백 수정
};

if (!isValidTransition) {
log.warn("Invalid status transition from {} to {}", currentStatus, newStatus);
Expand All @@ -329,14 +332,14 @@ public void validateStatusTransition(SubmissionStatus currentStatus, SubmissionS
}

//과제 제출 상태 ENUM 타입 검증
public SubmissionStatus validateSubmissionStatus(String statusString) {
public void validateSubmissionStatus(String statusString) {

if (statusString == null || statusString.trim().isEmpty()) {
throw new RestApiException(ErrorCode.INVALID_SUBMISSION_STATUS);
}

try {
return SubmissionStatus.valueOf(statusString.trim().toUpperCase());
SubmissionStatus.valueOf(statusString.trim().toUpperCase());
} catch (IllegalArgumentException e) {
log.warn("Invalid submission status provided: {}", statusString);
throw new RestApiException(ErrorCode.INVALID_SUBMISSION_STATUS);
Expand Down
Loading
Loading