diff --git a/src/main/java/org/wise/portal/domain/run/Run.java b/src/main/java/org/wise/portal/domain/run/Run.java index e2720d616..7b0f05f6f 100644 --- a/src/main/java/org/wise/portal/domain/run/Run.java +++ b/src/main/java/org/wise/portal/domain/run/Run.java @@ -397,4 +397,8 @@ public interface Run extends Persistable { boolean isLockedAfterEndDate(); void setLockedAfterEndDate(boolean isLockedAfterEndDate); + + String getConnectCode(); + + void setConnectCode(String connectCode); } diff --git a/src/main/java/org/wise/portal/domain/run/impl/RunImpl.java b/src/main/java/org/wise/portal/domain/run/impl/RunImpl.java index 5024e34aa..1ee2736c0 100644 --- a/src/main/java/org/wise/portal/domain/run/impl/RunImpl.java +++ b/src/main/java/org/wise/portal/domain/run/impl/RunImpl.java @@ -127,6 +127,9 @@ public class RunImpl implements Run { @Transient private static final String COLUMN_NAME_IS_LOCKED_AFTER_END_DATE = "isLockedAfterEndDate"; + @Transient + private static final String COLUMN_NAME_CK_PROJECT_CODE = "ckProjectCode"; + @Id @Getter @Setter @@ -247,6 +250,11 @@ public class RunImpl implements Run { @Setter private boolean isRandomPeriodAssignment = false; + @Column(name = RunImpl.COLUMN_NAME_CK_PROJECT_CODE) + @Getter + @Setter + private String connectCode; + public Group getPeriodByName(String periodName) throws PeriodNotFoundException { Set periods = getPeriods(); for (Group period : periods) { diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/ControllerUtil.java b/src/main/java/org/wise/portal/presentation/web/controllers/ControllerUtil.java index 82d1f5f42..242918626 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/ControllerUtil.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/ControllerUtil.java @@ -30,19 +30,27 @@ import java.util.Collection; import java.util.Date; import java.util.List; -import java.util.Properties; import java.util.Set; import javax.annotation.PostConstruct; import javax.net.ssl.HttpsURLConnection; +import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; +import org.apache.http.HttpEntity; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.util.EntityUtils; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; import org.springframework.security.acls.model.Permission; +import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolder; @@ -216,6 +224,7 @@ public static JSONObject getRunJSON(Run run) throws JSONException { runJSON.put("runCode", run.getRuncode()); runJSON.put("startTime", run.getStartTimeMilliseconds()); runJSON.put("endTime", run.getEndTimeMilliseconds()); + runJSON.put("code", run.getConnectCode()); Set periods = run.getPeriods(); JSONArray periodsArray = new JSONArray(); for (Group period : periods) { @@ -502,4 +511,56 @@ public static JSONObject createErrorResponse(String messageCode) { } return response; } + + /** + * Do a POST request to CK Backend for: + * 1. Linking run to CK Board project. + * 2. Unlinking run from CK Board project. + * 3. Adding members of a run to CK Board project. + */ + public static String doCkBoardPost(HttpServletRequest request, Authentication authentication, + String params, String url) { + try { + String ckSessionCookie = getCkSessionCookie(request); + if (ckSessionCookie != null) { + HttpClient client = HttpClientBuilder.create().build(); + HttpPost ckBoardLinkReq = new HttpPost(getCkBoardUrl(url)); + ckBoardLinkReq.addHeader("content-type", "application/json"); + ckBoardLinkReq.setHeader("Authorization", "Bearer " + ckSessionCookie); + ckBoardLinkReq.setEntity(new StringEntity(params, ContentType.APPLICATION_JSON)); + HttpEntity response = client.execute(ckBoardLinkReq).getEntity(); + return EntityUtils.toString(response); + } + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + public static String getCkBoardUrl(String apiEndpoint) { + String ckBoardUrl = appProperties.getProperty("ck_board_url"); + + // The CK Board local backend url is only used for local development and should only be set in + // local development environments. When we are running locally, we need the local IP and port of + // the CK Board backend because the SCORE API is served using Docker. If the SCORE API makes a + // request to localhost:8001, it won't be able to access the CK Board backend. This is because + // the SCORE API expects localhost to be within the container but the CK Board backend is not in + // the container. + String ckBoardLocalBackendUrl = appProperties.getProperty("ck_board_local_backend_url"); + if (ckBoardLocalBackendUrl != null && !ckBoardLocalBackendUrl.equals("")) { + ckBoardUrl = ckBoardLocalBackendUrl; + } + return ckBoardUrl + apiEndpoint; + } + + public static String getCkSessionCookie(HttpServletRequest request) { + Cookie[] cookies = request.getCookies(); + for (Cookie cookie : cookies) { + if (cookie.getName().equals("CK_SESSION")) { + return cookie.getValue(); + } + } + return null; + } + } diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/student/StudentAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/student/StudentAPIController.java index 920b50ff2..6ef5df6c3 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/student/StudentAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/student/StudentAPIController.java @@ -38,6 +38,7 @@ import org.apache.commons.lang3.RandomStringUtils; import org.hibernate.StaleObjectStateException; +import org.json.JSONObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ResponseEntity; @@ -263,7 +264,7 @@ private Set getUsers(String[] userIds) throws ObjectNotFoundException { * return a map containing an error field with an error string. */ @PostMapping("/run/register") - HashMap addStudentToRun(Authentication auth, + HashMap addStudentToRun(HttpServletRequest request, Authentication auth, @RequestParam("runCode") String runCode, @RequestParam("period") String period) { User user = userService.retrieveUserByUsername(auth.getName()); Run run = getRun(runCode); @@ -278,6 +279,19 @@ HashMap addStudentToRun(Authentication auth, while (currentLoopIndex < maxLoop) { try { studentService.addStudentToRun(user, projectCode); + String connectCode = run.getConnectCode(); + if (connectCode != null && !connectCode.equals("")) { + try { + String url = "/api/projects/score/addMember"; + JSONObject params = new JSONObject(); + params.put("username", user.getUserDetails().getUsername()); + params.put("role", "student"); + params.put("code", connectCode); + ControllerUtil.doCkBoardPost(request, auth, params.toString(), url); + } catch (Exception e) { + e.printStackTrace(); + } + } HashMap runMap = getRunMap(user, run); return runMap; } catch (ObjectNotFoundException e) { diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java index be5196c9e..1b9814644 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherAPIController.java @@ -12,6 +12,7 @@ import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.RandomStringUtils; +import org.json.JSONObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.ResponseEntity; @@ -62,6 +63,12 @@ public class TeacherAPIController extends UserAPIController { @Value("${google.clientSecret:}") private String googleClientSecret; + @Value("${ck_board_url}") + private String ckBoardUrl; + + @Value("${ck_board_local_backend_url}") + private String ckBoardBackendUrl; + @GetMapping("/runs") List> getRuns(Authentication auth, @RequestParam(required = false) Integer max) { @@ -469,4 +476,64 @@ HashMap editRunIsLockedAfterEndDate(Authentication authenticatio } return response; } + + @PostMapping("/run/score/link") + Map linkRunToCkProject(HttpServletRequest request, Authentication authentication, + @RequestParam("runId") Long runId, @RequestParam("code") String code) { + Map result = new HashMap<>(); + try { + User user = userService.retrieveUserByUsername(authentication.getName()); + Run run = runService.retrieveById(runId); + if (run.isTeacherAssociatedToThisRun(user)) { + String url = "/api/projects/score/link"; + JSONObject params = new JSONObject(); + params.put("runId", runId); + params.put("code", code); + String jsonRes = ControllerUtil.doCkBoardPost(request, authentication, params.toString(), + url); + JSONObject json = new JSONObject(jsonRes); + if (json.has("code") && json.getString("code").equals(code)) { + runService.updateCkProjectConnectCode(runId, code); + result.put("code", code); + } + if (json.has("message")) { + result.put("message", json.getString("message")); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return result; + } + + @PostMapping("/run/score/unlink") + Map ulinkRunFromCkProject(HttpServletRequest request, + Authentication authentication, @RequestParam("runId") Long runId, + @RequestParam("code") String code) { + Map result = new HashMap<>(); + try { + User user = userService.retrieveUserByUsername(authentication.getName()); + Run run = runService.retrieveById(runId); + if (run.isTeacherAssociatedToThisRun(user)) { + String url = "/api/projects/score/unlink"; + JSONObject params = new JSONObject(); + params.put("runId", runId); + params.put("code", code); + String jsonRes = ControllerUtil.doCkBoardPost(request, authentication, params.toString(), + url); + JSONObject json = new JSONObject(jsonRes); + if (json.has("code") && json.getString("code").equals(code)) { + runService.updateCkProjectConnectCode(runId, ""); + result.put("code", code); + } + if (json.has("message")) { + result.put("message", json.getString("message")); + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return result; + } + } diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRunPermissionsAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRunPermissionsAPIController.java index bf885c9b3..7c0f0443a 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRunPermissionsAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/TeacherRunPermissionsAPIController.java @@ -1,12 +1,21 @@ package org.wise.portal.presentation.web.controllers.teacher; +import javax.servlet.http.HttpServletRequest; + +import org.json.JSONException; +import org.json.JSONObject; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.core.Authentication; import org.springframework.web.bind.annotation.*; import org.wise.portal.dao.ObjectNotFoundException; +import org.wise.portal.domain.run.Run; +import org.wise.portal.domain.user.User; +import org.wise.portal.presentation.web.controllers.ControllerUtil; import org.wise.portal.presentation.web.exception.TeacherAlreadySharedWithRunException; import org.wise.portal.presentation.web.response.SharedOwner; import org.wise.portal.presentation.web.response.SimpleResponse; import org.wise.portal.service.run.RunService; +import org.wise.portal.service.user.UserService; /** * REST API endpoints for Teacher Permissions @@ -21,11 +30,29 @@ public class TeacherRunPermissionsAPIController { @Autowired private RunService runService; + @Autowired + private UserService userService; + @RequestMapping(value = "/{runId}/{teacherUsername}", method = RequestMethod.PUT) - protected SharedOwner addSharedOwner(@PathVariable Long runId, - @PathVariable String teacherUsername) { + protected SharedOwner addSharedOwner(HttpServletRequest request, Authentication auth, + @PathVariable Long runId, @PathVariable String teacherUsername) { try { - return runService.addSharedTeacher(runId, teacherUsername); + SharedOwner sharedOwner = runService.addSharedTeacher(runId, teacherUsername); + Run run = runService.retrieveById(runId); + String connectCode = run.getConnectCode(); + if (connectCode != null && !connectCode.equals("")) { + try { + String url = "/api/projects/score/addMember"; + JSONObject params = new JSONObject(); + params.put("username", teacherUsername); + params.put("role", "teacher"); + params.put("code", connectCode); + ControllerUtil.doCkBoardPost(request, auth, params.toString(), url); + } catch (Exception e) { + e.printStackTrace(); + } + } + return sharedOwner; } catch (ObjectNotFoundException e) { return null; } catch (TeacherAlreadySharedWithRunException e) { @@ -34,20 +61,49 @@ protected SharedOwner addSharedOwner(@PathVariable Long runId, } @PutMapping("/transfer/{runId}/{teacherUsername}") - protected String transferRunOwnership(@PathVariable Long runId, - @PathVariable String teacherUsername) { + protected String transferRunOwnership(HttpServletRequest request, Authentication auth, + @PathVariable Long runId, @PathVariable String teacherUsername) { try { - return runService.transferRunOwnership(runId, teacherUsername).toString(); + JSONObject result = runService.transferRunOwnership(runId, teacherUsername); + if (result != null) { + try { + String connectCode = result.getString("code"); + if (connectCode != null && !connectCode.equals("")) { + String url = "/api/projects/score/addMember"; + JSONObject params = new JSONObject(); + params.put("username", teacherUsername); + params.put("role", "teacher"); + params.put("code", connectCode); + ControllerUtil.doCkBoardPost(request, auth, params.toString(), url); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + return result.toString(); } catch (ObjectNotFoundException e) { return null; } } @RequestMapping(value = "/{runId}/{username}", method = RequestMethod.DELETE) - protected SimpleResponse removeSharedOwner(@PathVariable Long runId, - @PathVariable String username) { + protected SimpleResponse removeSharedOwner(HttpServletRequest request, Authentication auth, + @PathVariable Long runId, @PathVariable String username) { try { runService.removeSharedTeacher(username, runId); + Run run = runService.retrieveById(runId); + String connectCode = run.getConnectCode(); + if (connectCode != null && !connectCode.equals("")) { + try { + String url = "/api/projects/score/removeMember"; + JSONObject params = new JSONObject(); + params.put("username", username); + params.put("code", connectCode); + ControllerUtil.doCkBoardPost(request, auth, params.toString(), url); + } catch (JSONException e) { + e.printStackTrace(); + } + } return new SimpleResponse("success", "successfully removed shared owner"); } catch (ObjectNotFoundException e) { return new SimpleResponse("error", "user or run was not found"); diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/management/RemoveStudentRunController.java b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/management/RemoveStudentRunController.java index 6e4d1321e..a7c475f8b 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/teacher/management/RemoveStudentRunController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/teacher/management/RemoveStudentRunController.java @@ -1,5 +1,8 @@ package org.wise.portal.presentation.web.controllers.teacher.management; +import javax.servlet.http.HttpServletRequest; + +import org.json.JSONObject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.access.annotation.Secured; @@ -10,6 +13,7 @@ import org.wise.portal.dao.ObjectNotFoundException; import org.wise.portal.domain.run.Run; import org.wise.portal.domain.user.User; +import org.wise.portal.presentation.web.controllers.ControllerUtil; import org.wise.portal.service.run.RunService; import org.wise.portal.service.student.StudentService; import org.wise.portal.service.user.UserService; @@ -28,12 +32,24 @@ public class RemoveStudentRunController { private UserService userService; @DeleteMapping("/api/teacher/run/{runId}/student/{studentId}/remove") - void changeWorkgroupPeriod(Authentication auth, @PathVariable Long runId, - @PathVariable Long studentId) throws ObjectNotFoundException { + void changeWorkgroupPeriod(HttpServletRequest request, Authentication auth, + @PathVariable Long runId, @PathVariable Long studentId) throws ObjectNotFoundException { Run run = runService.retrieveById(runId); if (runService.hasWritePermission(auth, run)) { User studentUser = userService.retrieveById(studentId); studentService.removeStudentFromRun(studentUser, run); + String connectCode = run.getConnectCode(); + if (connectCode != null && !connectCode.equals("")) { + try { + String url = "/api/projects/score/removeMember"; + JSONObject params = new JSONObject(); + params.put("username", studentUser.getUserDetails().getUsername()); + params.put("code", connectCode); + ControllerUtil.doCkBoardPost(request, auth, params.toString(), url); + } catch (Exception e) { + e.printStackTrace(); + } + } } else { throw new AccessDeniedException("User does not have permission to remove student from run"); } diff --git a/src/main/java/org/wise/portal/presentation/web/controllers/user/UserAPIController.java b/src/main/java/org/wise/portal/presentation/web/controllers/user/UserAPIController.java index a24609268..7dcd6b5da 100644 --- a/src/main/java/org/wise/portal/presentation/web/controllers/user/UserAPIController.java +++ b/src/main/java/org/wise/portal/presentation/web/controllers/user/UserAPIController.java @@ -276,6 +276,7 @@ protected HashMap getRunMap(User user, Run run) { map.put("owner", convertUserToMap(run.getOwner())); map.put("numStudents", run.getNumStudents()); map.put("wiseVersion", run.getProject().getWiseVersion()); + map.put("connectCode", run.getConnectCode()); if (user.isStudent()) { addStudentInfoToRunMap(user, run, map); diff --git a/src/main/java/org/wise/portal/service/run/RunService.java b/src/main/java/org/wise/portal/service/run/RunService.java index c9922b1bf..baf7dbff9 100644 --- a/src/main/java/org/wise/portal/service/run/RunService.java +++ b/src/main/java/org/wise/portal/service/run/RunService.java @@ -189,6 +189,10 @@ void addSharedTeacherPermission(Long runId, Long userId, Integer permissionId) void removeSharedTeacherPermission(Long runId, Long userId, Integer permissionId) throws ObjectNotFoundException; + @Secured({ "ROLE_TEACHER" }) + @Transactional() + void updateCkProjectConnectCode(Long runId, String code) throws ObjectNotFoundException; + /** * Removes specified teacher user from specified run. Also removes any shared permission on the * project of the run. diff --git a/src/main/java/org/wise/portal/service/run/impl/RunServiceImpl.java b/src/main/java/org/wise/portal/service/run/impl/RunServiceImpl.java index 76e52d4eb..25589d16e 100644 --- a/src/main/java/org/wise/portal/service/run/impl/RunServiceImpl.java +++ b/src/main/java/org/wise/portal/service/run/impl/RunServiceImpl.java @@ -447,6 +447,12 @@ public void removeSharedTeacherPermission(Long runId, Long userId, Integer permi } } + public void updateCkProjectConnectCode(Long runId, String code) throws ObjectNotFoundException { + Run run = retrieveById(runId); + run.setConnectCode(code); + runDao.save(run); + } + @Transactional public void removeSharedTeacher(String username, Long runId) throws ObjectNotFoundException { Run run = retrieveById(runId); diff --git a/src/main/java/org/wise/portal/service/session/impl/SessionServiceImpl.java b/src/main/java/org/wise/portal/service/session/impl/SessionServiceImpl.java index 8ab212aac..24d0c75be 100644 --- a/src/main/java/org/wise/portal/service/session/impl/SessionServiceImpl.java +++ b/src/main/java/org/wise/portal/service/session/impl/SessionServiceImpl.java @@ -18,6 +18,7 @@ import org.springframework.stereotype.Service; import org.wise.portal.domain.authentication.impl.StudentUserDetails; import org.wise.portal.domain.authentication.impl.TeacherUserDetails; +import org.wise.portal.presentation.web.controllers.ControllerUtil; import org.wise.portal.service.session.SessionService; @Service @@ -108,9 +109,9 @@ public boolean isCkBoardAvailable() { } public void signOutOfCkBoard(HttpServletRequest request) { - String ckSessionCookie = getCkSessionCookie(request); + String ckSessionCookie = ControllerUtil.getCkSessionCookie(request); HttpClient client = HttpClientBuilder.create().build(); - HttpPost ckBoardLogoutRequest = new HttpPost(getCkBoardLogoutUrl()); + HttpPost ckBoardLogoutRequest = new HttpPost(ControllerUtil.getCkBoardUrl("/api/auth/logout")); ckBoardLogoutRequest.setHeader("Authorization", "Bearer " + ckSessionCookie); try { client.execute(ckBoardLogoutRequest); @@ -119,30 +120,4 @@ public void signOutOfCkBoard(HttpServletRequest request) { } } - private String getCkBoardLogoutUrl() { - String ckBoardUrl = appProperties.getProperty("ck_board_url"); - - // The CK Board local backend url is only used for local development and should only be set in - // local development environments. When we are running locally, we need the local IP and port of - // the CK Board backend because the SCORE API is served using Docker. If the SCORE API makes a - // request to localhost:8001, it won't be able to access the CK Board backend. This is because - // the SCORE API expects localhost to be within the container but the CK Board backend is not in - // the container. - String ckBoardLocalBackendUrl = appProperties.getProperty("ck_board_local_backend_url"); - if (ckBoardLocalBackendUrl != null && !ckBoardLocalBackendUrl.equals("")) { - ckBoardUrl = ckBoardLocalBackendUrl; - } - return ckBoardUrl + "/api/auth/logout"; - } - - private String getCkSessionCookie(HttpServletRequest request) { - Cookie[] cookies = request.getCookies(); - for (Cookie cookie : cookies) { - if (cookie.getName().equals("CK_SESSION")) { - return cookie.getValue(); - } - } - return null; - } - }