Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(core): add course submission mode #1704

Draft
wants to merge 1 commit into
base: dev
Choose a base branch
from
Draft
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
3 changes: 3 additions & 0 deletions modules/fbs-core/api/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ services:
ids:
salt: ${ID_SALT:feedbacksystem_id_salt}
length: ${ID_LENGTH:8}
ip:
vpnCidrs: ${IP_VPN_CIDR:""}
localCidrs: ${IP_LOCAL_CIDR:""}
spring:
main:
allow-bean-definition-overriding: true
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
BEGIN;

ALTER TABLE course ADD COLUMN submission_mode ENUM('internet', 'vpn', 'local') NOT NULL DEFAULT 'internet';

INSERT INTO migration (number) VALUES (23);

COMMIT;

Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,13 @@ class CourseController {
body.retrive("semesterId").asInt(),
body.retrive("name").asText(),
body.retrive("description").asText(),
body.retrive("visible").asBool()
body.retrive("visible").asBool(),
body.retrive("submissionMode").asText(),
) match {
case (semesterId, Some(name), desc, visible) =>
courseService.create(Course(name, desc.getOrElse(""), visible.getOrElse(true), semesterId = semesterId))
case (semesterId, Some(name), desc, visible, submissionMode) =>
courseService.create(
Course(name, desc.getOrElse(""), visible.getOrElse(true), semesterId = semesterId, submissionMode = submissionMode.getOrElse("internet"))
)
case _ => throw new BadRequestException("Malformed Request Body")
}
}
Expand Down Expand Up @@ -128,10 +131,14 @@ class CourseController {
body.retrive("semesterId").asInt(),
body.retrive("name").asText(),
body.retrive("description").asText(),
body.retrive("visible").asBool()
body.retrive("visible").asBool(),
body.retrive("submissionMode").asText(),
) match {
case (semesterId, Some(name), desc, visible) =>
courseService.update(cid, Course(name, desc.getOrElse(""), visible.getOrElse(true), semesterId = semesterId))
case (semesterId, Some(name), desc, visible, submissionMode) =>
courseService.update(
cid,
Course(name, desc.getOrElse(""), visible.getOrElse(true), semesterId = semesterId, submissionMode = submissionMode.getOrElse("internet"))
)
case _ => throw new BadRequestException("Malformed Request Body")
}
case _ => throw new ForbiddenException()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import de.thm.ii.fbs.model.{CourseRole, GlobalRole, Submission}
import de.thm.ii.fbs.services.checker.CheckerServiceFactoryService
import de.thm.ii.fbs.services.persistence._
import de.thm.ii.fbs.services.persistence.storage.{MinioStorageService, StorageService}
import de.thm.ii.fbs.services.security.AuthService
import de.thm.ii.fbs.services.security.{AuthService, IpService}
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.core.io.InputStreamResource
Expand Down Expand Up @@ -52,7 +52,11 @@ class SubmissionController {
@Autowired
private val courseService: CourseService = null
@Autowired
private val ipService: IpService = null
@Autowired
private val courseRegistration: CourseRegistrationService = null
@Autowired
private val request: HttpServletRequest = null
private val objectMapper = new ObjectMapper();
private val logger = LoggerFactory.getLogger(this.getClass)

Expand Down Expand Up @@ -137,6 +141,14 @@ class SubmissionController {
def submit(@PathVariable("uid") uid: Int, @PathVariable("cid") cid: Int, @PathVariable("tid") tid: Int,
@RequestParam file: MultipartFile, @RequestParam additionalInformation: Optional[String],
req: HttpServletRequest, res: HttpServletResponse): Submission = {
val course = courseService.find(cid)
course match {
case Some(course) =>
if (!ipService.isIpInZone(course.submissionMode, Option(request.getHeader("X-Real-IP")).getOrElse(request.getRemoteAddr))) {
throw new ForbiddenException()
}
case None => throw new ResourceNotFoundException()
}
val user = authService.authorize(req, res)
val someCourseRole = courseRegistration.getCourseRoleOfUser(cid, user.id)
val noPrivateAccess = someCourseRole.contains(CourseRole.STUDENT) && user.globalRole != GlobalRole.ADMIN
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ package de.thm.ii.fbs.model
* @param groupSelection Whether registration for groups is possible
*/
case class Course(name: String, description: String = "", visible: Boolean = true, id: Int = 0, semesterId: Option[Int] = None,
groupSelection: Option[Boolean] = None)
groupSelection: Option[Boolean] = None, submissionMode: String = "internet")
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ class CourseService {
* @return List of courses
*/
def findByPattern(pattern: String, ignoreHidden: Boolean = true): List[Course] = DB.query(
"SELECT course_id, group_selection, semester_id, name, description, visible FROM course WHERE name like ?" + (if (ignoreHidden) " AND visible = 1" else ""),
"SELECT course_id, group_selection, semester_id, name, description, visible, " +
"submission_mode FROM course WHERE name like ?" + (if (ignoreHidden) " AND visible = 1" else ""),
(res, _) => parseResult(res), "%" + pattern + "%")

/**
Expand All @@ -45,7 +46,7 @@ class CourseService {
* @return The found course
*/
def find(id: Int): Option[Course] = DB.query(
"SELECT course_id, group_selection, semester_id, name, description, visible, group_selection FROM course WHERE course_id = ?",
"SELECT course_id, group_selection, semester_id, name, description, visible, group_selection, submission_mode FROM course WHERE course_id = ?",
(res, _) => parseResult(res), id).headOption

/**
Expand All @@ -55,8 +56,8 @@ class CourseService {
* @return The created course with id
*/
def create(course: Course): Course = {
DB.insert("INSERT INTO course (semester_id, name, description, visible) VALUES (?,?,?,?);",
course.semesterId.orNull, course.name, course.description, course.visible)
DB.insert("INSERT INTO course (semester_id, name, description, visible, submission_mode) VALUES (?,?,?,?,?);",
course.semesterId.orNull, course.name, course.description, course.visible, course.submissionMode)
.map(gk => gk(0).asInstanceOf[BigInteger].intValue())
.flatMap(id => find(id)) match {
case Some(course) => course
Expand All @@ -72,8 +73,8 @@ class CourseService {
* @return True if successful
*/
def update(cid: Int, course: Course): Boolean = {
1 == DB.update("UPDATE course SET semester_id = ?, name = ?, description = ?, visible = ? WHERE course_id = ?",
course.semesterId.orNull, course.name, course.description, course.visible, cid)
1 == DB.update("UPDATE course SET semester_id = ?, name = ?, description = ?, visible = ?, submission_mode = ? WHERE course_id = ?",
course.semesterId.orNull, course.name, course.description, course.visible, course.submissionMode, cid)
}

/**
Expand All @@ -90,6 +91,7 @@ class CourseService {
name = res.getString("name"),
description = res.getString("description"),
visible = res.getBoolean("visible"),
submissionMode = res.getString("submission_mode"),
id = res.getInt("course_id")
)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package de.thm.ii.fbs.services.security

import org.springframework.beans.factory.annotation.Value
import org.springframework.stereotype.Component
import org.springframework.security.web.util.matcher.IpAddressMatcher

@Component
class IpService {
@Value("${services.ip.vpnCidrs}") private var vpnIpRangeString: String = ""
@Value("${services.ip.localCidrs}") private var localIpRangeString: String = ""

private def vpnIpRange: Seq[String] = vpnIpRangeString.split(",").filter(str => str.nonEmpty)
private def localIpRange: Seq[String] = localIpRangeString.split(",").filter(str => str.nonEmpty)

private def isInCidr(cidr: String, ip: String): Boolean = {
new IpAddressMatcher(cidr).matches(ip)
}

private def ipInCidrs(cirds: Seq[String], ip: String): Boolean =
cirds.exists(cidr => isInCidr(cidr, ip))

def isIpInZone(zone: String, ip: String): Boolean =
zone match {
case "internet" => true
case "vpn" => ipInCidrs(vpnIpRange, ip) || ipInCidrs(localIpRange, ip)
case "local" => ipInCidrs(localIpRange, ip) && !ipInCidrs(vpnIpRange, ip)
}
}
Loading