Merge remote-tracking branch 'origin/dev-1.1.0' into development
Conflicts: src/main/java/ch/ethz/seb/sebserver/gui/content/MonitoringRunningExam.java src/main/java/ch/ethz/seb/sebserver/gui/service/page/impl/JitsiMeetProctoringView.java src/main/java/ch/ethz/seb/sebserver/gui/service/session/ProctoringGUIService.java src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/RemoteProctoringRoomDAO.java src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/dao/impl/RemoteProctoringRoomDAOImpl.java src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/ExamProctoringRoomService.java src/main/java/ch/ethz/seb/sebserver/webservice/servicelayer/session/impl/ExamProctoringRoomServiceImpl.java src/main/java/ch/ethz/seb/sebserver/webservice/weblayer/api/ExamProctoringController.java
This commit is contained in:
commit
6ff8b703c9
13 changed files with 402 additions and 194 deletions
|
@ -40,6 +40,9 @@ import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
|||
@Order(7)
|
||||
public class WebSecurityConfig extends WebSecurityConfigurerAdapter implements ErrorController {
|
||||
|
||||
private static final String ERROR_PATH = "/sebserver/error";
|
||||
private static final String CHECK_PATH = "/sebserver/check";
|
||||
|
||||
@Value("${sebserver.webservice.http.redirect.gui}")
|
||||
private String guiRedirect;
|
||||
@Value("${sebserver.webservice.api.exam.endpoint.discovery}")
|
||||
|
@ -78,22 +81,27 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter implements E
|
|||
public void configure(final WebSecurity web) {
|
||||
web
|
||||
.ignoring()
|
||||
.antMatchers("/error")
|
||||
.antMatchers(ERROR_PATH)
|
||||
.antMatchers(CHECK_PATH)
|
||||
.antMatchers(this.examAPIDiscoveryEndpoint)
|
||||
.antMatchers(this.adminAPIEndpoint + API.INFO_ENDPOINT + API.LOGO_PATH_SEGMENT + "/**")
|
||||
.antMatchers(this.adminAPIEndpoint + API.INFO_ENDPOINT + API.INFO_INST_PATH_SEGMENT + "/**")
|
||||
.antMatchers(this.adminAPIEndpoint + API.REGISTER_ENDPOINT);
|
||||
}
|
||||
|
||||
@RequestMapping("/error")
|
||||
@RequestMapping(CHECK_PATH)
|
||||
public void check() throws IOException {
|
||||
}
|
||||
|
||||
@RequestMapping(ERROR_PATH)
|
||||
public void handleError(final HttpServletResponse response) throws IOException {
|
||||
//response.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY);
|
||||
response.getOutputStream().print(response.getStatus());
|
||||
response.setHeader(HttpHeaders.LOCATION, this.guiRedirect);
|
||||
response.flushBuffer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getErrorPath() {
|
||||
return "/error";
|
||||
return ERROR_PATH;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -185,6 +185,7 @@ public final class API {
|
|||
public static final String EXAM_PROCTORING_ROOM_CONNECTIONS_PATH_SEGMENT = "/room-connections";
|
||||
public static final String EXAM_PROCTORING_ACTIVATE_TOWNHALL_ROOM = "activate-towhall-room";
|
||||
public static final String EXAM_PROCTORING_TOWNHALL_ROOM_DATA = "towhall-room-data";
|
||||
public static final String EXAM_PROCTORING_TOWNHALL_ROOM_AVAILABLE = "towhall-available";
|
||||
|
||||
public static final String EXAM_PROCTORING_ATTR_RECEIVE_AUDIO = "receive_audio";
|
||||
public static final String EXAM_PROCTORING_ATTR_RECEIVE_VIDEO = "receive_video";
|
||||
|
|
|
@ -116,60 +116,56 @@ public final class InstitutionalAuthenticationEntryPoint implements Authenticati
|
|||
|
||||
final String institutionalEndpoint = extractInstitutionalEndpoint(request);
|
||||
|
||||
if (StringUtils.isNoneBlank(institutionalEndpoint) && log.isDebugEnabled()) {
|
||||
log.debug("No default gui entrypoint requested: {}", institutionalEndpoint);
|
||||
} else {
|
||||
request.getSession().setAttribute(INST_SUFFIX_ATTRIBUTE, null);
|
||||
request.getSession().removeAttribute(API.PARAM_LOGO_IMAGE);
|
||||
forwardToEntryPoint(request, response, this.guiEntryPoint, false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
|
||||
final RestTemplate restTemplate = new RestTemplate();
|
||||
final List<EntityName> institutions = restTemplate
|
||||
.exchange(
|
||||
this.webserviceURIService.getURIBuilder()
|
||||
.path(API.INFO_ENDPOINT + API.INFO_INST_ENDPOINT)
|
||||
.toUriString(),
|
||||
HttpMethod.GET,
|
||||
HttpEntity.EMPTY,
|
||||
new ParameterizedTypeReference<List<EntityName>>() {
|
||||
},
|
||||
institutionalEndpoint,
|
||||
API.INFO_PARAM_INST_SUFFIX,
|
||||
institutionalEndpoint)
|
||||
.getBody();
|
||||
|
||||
if (institutions != null && !institutions.isEmpty()) {
|
||||
request.getSession().setAttribute(
|
||||
INST_SUFFIX_ATTRIBUTE,
|
||||
StringUtils.isNotBlank(institutionalEndpoint)
|
||||
? institutionalEndpoint
|
||||
: null);
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Known and active gui entrypoint requested: {}", institutions);
|
||||
}
|
||||
|
||||
final String logoImageBase64 = requestLogoImage(institutionalEndpoint);
|
||||
if (StringUtils.isNotBlank(logoImageBase64)) {
|
||||
request.getSession().setAttribute(API.PARAM_LOGO_IMAGE, logoImageBase64);
|
||||
|
||||
}
|
||||
forwardToEntryPoint(request, response, this.guiEntryPoint, false);
|
||||
return;
|
||||
if (StringUtils.isNotBlank(institutionalEndpoint)) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("No default gui entrypoint requested: {}", institutionalEndpoint);
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
log.error("Failed to extract and set institutional endpoint request: ", e);
|
||||
|
||||
try {
|
||||
|
||||
final RestTemplate restTemplate = new RestTemplate();
|
||||
final List<EntityName> institutions = restTemplate
|
||||
.exchange(
|
||||
this.webserviceURIService.getURIBuilder()
|
||||
.path(API.INFO_ENDPOINT + API.INFO_INST_ENDPOINT)
|
||||
.toUriString(),
|
||||
HttpMethod.GET,
|
||||
HttpEntity.EMPTY,
|
||||
new ParameterizedTypeReference<List<EntityName>>() {
|
||||
},
|
||||
institutionalEndpoint,
|
||||
API.INFO_PARAM_INST_SUFFIX,
|
||||
institutionalEndpoint)
|
||||
.getBody();
|
||||
|
||||
if (institutions != null && !institutions.isEmpty()) {
|
||||
request.getSession().setAttribute(
|
||||
INST_SUFFIX_ATTRIBUTE,
|
||||
StringUtils.isNotBlank(institutionalEndpoint)
|
||||
? institutionalEndpoint
|
||||
: null);
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Known and active gui entrypoint requested: {}", institutions);
|
||||
}
|
||||
|
||||
final String logoImageBase64 = requestLogoImage(institutionalEndpoint);
|
||||
if (StringUtils.isNotBlank(logoImageBase64)) {
|
||||
request.getSession().setAttribute(API.PARAM_LOGO_IMAGE, logoImageBase64);
|
||||
|
||||
}
|
||||
forwardToEntryPoint(request, response, this.guiEntryPoint, false);
|
||||
return;
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
log.error("Failed to extract and set institutional endpoint request: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
request.getSession().setAttribute(INST_SUFFIX_ATTRIBUTE, null);
|
||||
request.getSession().removeAttribute(API.PARAM_LOGO_IMAGE);
|
||||
response.setStatus(HttpStatus.UNAUTHORIZED.value());
|
||||
forwardToEntryPoint(request, response, this.guiEntryPoint, true);
|
||||
forwardToEntryPoint(request, response, this.guiEntryPoint, institutionalEndpoint == null);
|
||||
|
||||
}
|
||||
|
||||
|
@ -203,16 +199,25 @@ public final class InstitutionalAuthenticationEntryPoint implements Authenticati
|
|||
|
||||
public static String extractInstitutionalEndpoint(final HttpServletRequest request) {
|
||||
final String requestURI = request.getRequestURI();
|
||||
if (StringUtils.isBlank(requestURI) || requestURI.equals(Constants.SLASH.toString())) {
|
||||
if (StringUtils.isBlank(requestURI)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Trying to verify institution from requested entrypoint url: {}", requestURI);
|
||||
if (requestURI.equals(Constants.SLASH.toString())) {
|
||||
return StringUtils.EMPTY;
|
||||
}
|
||||
|
||||
try {
|
||||
return requestURI.substring(requestURI.lastIndexOf(Constants.SLASH) + 1);
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Trying to verify institution from requested entrypoint url: {}", requestURI);
|
||||
}
|
||||
|
||||
final String[] split = StringUtils.split(requestURI, Constants.SLASH);
|
||||
if (split.length > 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return split[0];
|
||||
} catch (final Exception e) {
|
||||
log.error("Failed to extract institutional URL suffix: {}", e.getMessage());
|
||||
return null;
|
||||
|
|
|
@ -17,6 +17,7 @@ import java.util.function.BooleanSupplier;
|
|||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.eclipse.rap.rwt.RWT;
|
||||
import org.eclipse.rap.rwt.client.service.JavaScriptExecutor;
|
||||
import org.eclipse.swt.SWT;
|
||||
|
@ -77,7 +78,7 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetProctorin
|
|||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetClientConnectionDataList;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetCollectingRooms;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetProctorRoomConnection;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetTownhallRoom;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.IsTownhallRoomAvailable;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
|
||||
import ch.ethz.seb.sebserver.gui.service.session.ClientConnectionTable;
|
||||
import ch.ethz.seb.sebserver.gui.service.session.InstructionProcessor;
|
||||
|
@ -123,6 +124,7 @@ public class MonitoringRunningExam implements TemplateComposer {
|
|||
|
||||
private final ServerPushService serverPushService;
|
||||
private final PageService pageService;
|
||||
private final RestService restService;
|
||||
private final ResourceService resourceService;
|
||||
private final AsyncRunner asyncRunner;
|
||||
private final InstructionProcessor instructionProcessor;
|
||||
|
@ -147,6 +149,7 @@ public class MonitoringRunningExam implements TemplateComposer {
|
|||
|
||||
this.serverPushService = serverPushService;
|
||||
this.pageService = pageService;
|
||||
this.restService = pageService.getRestService();
|
||||
this.resourceService = pageService.getResourceService();
|
||||
this.asyncRunner = asyncRunner;
|
||||
this.instructionProcessor = instructionProcessor;
|
||||
|
@ -214,13 +217,14 @@ public class MonitoringRunningExam implements TemplateComposer {
|
|||
|
||||
this.serverPushService.runServerPush(
|
||||
new ServerPushContext(
|
||||
content, Utils.truePredicate(),
|
||||
content,
|
||||
Utils.truePredicate(),
|
||||
createServerPushUpdateErrorHandler(this.pageService, pageContext)),
|
||||
this.pollInterval,
|
||||
context -> clientTable.updateValues(),
|
||||
updateTableGUI(clientTable));
|
||||
|
||||
final BooleanSupplier privilege = () -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER);
|
||||
final BooleanSupplier isExamSupporter = () -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER);
|
||||
|
||||
actionBuilder
|
||||
|
||||
|
@ -242,20 +246,20 @@ public class MonitoringRunningExam implements TemplateComposer {
|
|||
|
||||
return copyOfPageAction;
|
||||
})
|
||||
.publishIf(privilege, false)
|
||||
.publishIf(isExamSupporter, false)
|
||||
|
||||
.newAction(ActionDefinition.MONITOR_EXAM_QUIT_ALL)
|
||||
.withEntityKey(entityKey)
|
||||
.withConfirm(() -> CONFIRM_QUIT_ALL)
|
||||
.withExec(action -> this.quitSEBClients(action, clientTable, true))
|
||||
.noEventPropagation()
|
||||
.publishIf(privilege)
|
||||
.publishIf(isExamSupporter)
|
||||
|
||||
.newAction(ActionDefinition.MONITORING_EXAM_SEARCH_CONNECTIONS)
|
||||
.withEntityKey(entityKey)
|
||||
.withExec(this::openSearchPopup)
|
||||
.noEventPropagation()
|
||||
.publishIf(privilege)
|
||||
.publishIf(isExamSupporter)
|
||||
|
||||
.newAction(ActionDefinition.MONITOR_EXAM_QUIT_SELECTED)
|
||||
.withEntityKey(entityKey)
|
||||
|
@ -265,7 +269,7 @@ public class MonitoringRunningExam implements TemplateComposer {
|
|||
action -> this.quitSEBClients(action, clientTable, false),
|
||||
EMPTY_ACTIVE_SELECTION_TEXT_KEY)
|
||||
.noEventPropagation()
|
||||
.publishIf(privilege, false)
|
||||
.publishIf(isExamSupporter, false)
|
||||
|
||||
.newAction(ActionDefinition.MONITOR_EXAM_DISABLE_SELECTED_CONNECTION)
|
||||
.withEntityKey(entityKey)
|
||||
|
@ -275,81 +279,26 @@ public class MonitoringRunningExam implements TemplateComposer {
|
|||
action -> this.disableSEBClients(action, clientTable, false),
|
||||
EMPTY_SELECTION_TEXT_KEY)
|
||||
.noEventPropagation()
|
||||
.publishIf(privilege, false);
|
||||
|
||||
if (privilege.getAsBoolean()) {
|
||||
|
||||
if (clientTable.isStatusHidden(ConnectionStatus.CLOSED)) {
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_CLOSED_CONNECTION)
|
||||
.withExec(showStateViewAction(clientTable, ConnectionStatus.CLOSED))
|
||||
.noEventPropagation()
|
||||
.withSwitchAction(
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_CLOSED_CONNECTION)
|
||||
.withExec(hideStateViewAction(clientTable, ConnectionStatus.CLOSED))
|
||||
.noEventPropagation()
|
||||
.create())
|
||||
.publish();
|
||||
} else {
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_CLOSED_CONNECTION)
|
||||
.withExec(hideStateViewAction(clientTable, ConnectionStatus.CLOSED))
|
||||
.noEventPropagation()
|
||||
.withSwitchAction(
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_CLOSED_CONNECTION)
|
||||
.withExec(showStateViewAction(clientTable, ConnectionStatus.CLOSED))
|
||||
.noEventPropagation()
|
||||
.create())
|
||||
.publish();
|
||||
}
|
||||
|
||||
if (clientTable.isStatusHidden(ConnectionStatus.CONNECTION_REQUESTED)) {
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_REQUESTED_CONNECTION)
|
||||
.withExec(showStateViewAction(clientTable, ConnectionStatus.CONNECTION_REQUESTED))
|
||||
.noEventPropagation()
|
||||
.withSwitchAction(
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_REQUESTED_CONNECTION)
|
||||
.withExec(
|
||||
hideStateViewAction(clientTable, ConnectionStatus.CONNECTION_REQUESTED))
|
||||
.noEventPropagation()
|
||||
.create())
|
||||
.publish();
|
||||
} else {
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_REQUESTED_CONNECTION)
|
||||
.withExec(hideStateViewAction(clientTable, ConnectionStatus.CONNECTION_REQUESTED))
|
||||
.noEventPropagation()
|
||||
.withSwitchAction(
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_REQUESTED_CONNECTION)
|
||||
.withExec(
|
||||
showStateViewAction(clientTable, ConnectionStatus.CONNECTION_REQUESTED))
|
||||
.noEventPropagation()
|
||||
.create())
|
||||
.publish();
|
||||
}
|
||||
|
||||
if (clientTable.isStatusHidden(ConnectionStatus.DISABLED)) {
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_DISABLED_CONNECTION)
|
||||
.withExec(showStateViewAction(clientTable, ConnectionStatus.DISABLED))
|
||||
.noEventPropagation()
|
||||
.withSwitchAction(
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_DISABLED_CONNECTION)
|
||||
.withExec(hideStateViewAction(clientTable, ConnectionStatus.DISABLED))
|
||||
.noEventPropagation()
|
||||
.create())
|
||||
.publish();
|
||||
} else {
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_DISABLED_CONNECTION)
|
||||
.withExec(hideStateViewAction(clientTable, ConnectionStatus.DISABLED))
|
||||
.noEventPropagation()
|
||||
.withSwitchAction(
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_DISABLED_CONNECTION)
|
||||
.withExec(showStateViewAction(clientTable, ConnectionStatus.DISABLED))
|
||||
.noEventPropagation()
|
||||
.create())
|
||||
.publish();
|
||||
}
|
||||
.publishIf(isExamSupporter, false);
|
||||
|
||||
if (isExamSupporter.getAsBoolean()) {
|
||||
addFilterActions(actionBuilder, clientTable, isExamSupporter);
|
||||
addProctoringActions(
|
||||
currentUser.getProctoringGUIService(),
|
||||
pageContext,
|
||||
content,
|
||||
actionBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
final ProctoringServiceSettings proctoringSettings = restService
|
||||
private void addProctoringActions(
|
||||
final ProctoringGUIService proctoringGUIService,
|
||||
final PageContext pageContext,
|
||||
final Composite parent,
|
||||
final PageActionBuilder actionBuilder) {
|
||||
|
||||
final EntityKey entityKey = pageContext.getEntityKey();
|
||||
final ProctoringServiceSettings proctoringSettings = this.restService
|
||||
.getBuilder(GetProctoringSettings.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
|
||||
.call()
|
||||
|
@ -359,7 +308,7 @@ public class MonitoringRunningExam implements TemplateComposer {
|
|||
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM)
|
||||
.withEntityKey(entityKey)
|
||||
.withExec(this::toggleTownhallRoom)
|
||||
.withExec(action -> this.toggleTownhallRoom(proctoringGUIService, action))
|
||||
.noEventPropagation()
|
||||
.publish();
|
||||
|
||||
|
@ -375,34 +324,126 @@ public class MonitoringRunningExam implements TemplateComposer {
|
|||
|
||||
final Map<String, Pair<RemoteProctoringRoom, TreeItem>> availableRooms = new HashMap<>();
|
||||
updateRoomActions(
|
||||
entityKey,
|
||||
pageContext,
|
||||
availableRooms,
|
||||
actionBuilder,
|
||||
proctoringSettings);
|
||||
proctoringSettings,
|
||||
proctoringGUIService);
|
||||
this.serverPushService.runServerPush(
|
||||
new ServerPushContext(
|
||||
content,
|
||||
parent,
|
||||
Utils.truePredicate(),
|
||||
createServerPushUpdateErrorHandler(this.pageService, pageContext)),
|
||||
this.proctoringRoomUpdateInterval,
|
||||
context -> updateRoomActions(
|
||||
entityKey,
|
||||
pageContext,
|
||||
availableRooms,
|
||||
actionBuilder,
|
||||
proctoringSettings));
|
||||
proctoringSettings,
|
||||
proctoringGUIService));
|
||||
}
|
||||
}
|
||||
|
||||
private void addFilterActions(
|
||||
final PageActionBuilder actionBuilder,
|
||||
final ClientConnectionTable clientTable,
|
||||
final BooleanSupplier isExamSupporter) {
|
||||
|
||||
addClosedFilterAction(actionBuilder, clientTable);
|
||||
addRequestedFilterAction(actionBuilder, clientTable);
|
||||
addDisabledFilterAction(actionBuilder, clientTable);
|
||||
}
|
||||
|
||||
private void addDisabledFilterAction(
|
||||
final PageActionBuilder actionBuilder,
|
||||
final ClientConnectionTable clientTable) {
|
||||
|
||||
if (clientTable.isStatusHidden(ConnectionStatus.DISABLED)) {
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_DISABLED_CONNECTION)
|
||||
.withExec(showStateViewAction(clientTable, ConnectionStatus.DISABLED))
|
||||
.noEventPropagation()
|
||||
.withSwitchAction(
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_DISABLED_CONNECTION)
|
||||
.withExec(hideStateViewAction(clientTable, ConnectionStatus.DISABLED))
|
||||
.noEventPropagation()
|
||||
.create())
|
||||
.publish();
|
||||
} else {
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_DISABLED_CONNECTION)
|
||||
.withExec(hideStateViewAction(clientTable, ConnectionStatus.DISABLED))
|
||||
.noEventPropagation()
|
||||
.withSwitchAction(
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_DISABLED_CONNECTION)
|
||||
.withExec(showStateViewAction(clientTable, ConnectionStatus.DISABLED))
|
||||
.noEventPropagation()
|
||||
.create())
|
||||
.publish();
|
||||
}
|
||||
}
|
||||
|
||||
private void addRequestedFilterAction(
|
||||
final PageActionBuilder actionBuilder,
|
||||
final ClientConnectionTable clientTable) {
|
||||
|
||||
if (clientTable.isStatusHidden(ConnectionStatus.CONNECTION_REQUESTED)) {
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_REQUESTED_CONNECTION)
|
||||
.withExec(showStateViewAction(clientTable, ConnectionStatus.CONNECTION_REQUESTED))
|
||||
.noEventPropagation()
|
||||
.withSwitchAction(
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_REQUESTED_CONNECTION)
|
||||
.withExec(
|
||||
hideStateViewAction(clientTable, ConnectionStatus.CONNECTION_REQUESTED))
|
||||
.noEventPropagation()
|
||||
.create())
|
||||
.publish();
|
||||
} else {
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_REQUESTED_CONNECTION)
|
||||
.withExec(hideStateViewAction(clientTable, ConnectionStatus.CONNECTION_REQUESTED))
|
||||
.noEventPropagation()
|
||||
.withSwitchAction(
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_REQUESTED_CONNECTION)
|
||||
.withExec(
|
||||
showStateViewAction(clientTable, ConnectionStatus.CONNECTION_REQUESTED))
|
||||
.noEventPropagation()
|
||||
.create())
|
||||
.publish();
|
||||
}
|
||||
}
|
||||
|
||||
private void addClosedFilterAction(
|
||||
final PageActionBuilder actionBuilder,
|
||||
final ClientConnectionTable clientTable) {
|
||||
|
||||
if (clientTable.isStatusHidden(ConnectionStatus.CLOSED)) {
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_CLOSED_CONNECTION)
|
||||
.withExec(showStateViewAction(clientTable, ConnectionStatus.CLOSED))
|
||||
.noEventPropagation()
|
||||
.withSwitchAction(
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_CLOSED_CONNECTION)
|
||||
.withExec(hideStateViewAction(clientTable, ConnectionStatus.CLOSED))
|
||||
.noEventPropagation()
|
||||
.create())
|
||||
.publish();
|
||||
} else {
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_HIDE_CLOSED_CONNECTION)
|
||||
.withExec(hideStateViewAction(clientTable, ConnectionStatus.CLOSED))
|
||||
.noEventPropagation()
|
||||
.withSwitchAction(
|
||||
actionBuilder.newAction(ActionDefinition.MONITOR_EXAM_SHOW_CLOSED_CONNECTION)
|
||||
.withExec(showStateViewAction(clientTable, ConnectionStatus.CLOSED))
|
||||
.noEventPropagation()
|
||||
.create())
|
||||
.publish();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isTownhallRoomActive(final String examModelId) {
|
||||
final RemoteProctoringRoom townhall = this.pageService.getRestService()
|
||||
.getBuilder(GetTownhallRoom.class)
|
||||
return !BooleanUtils.toBoolean(this.pageService
|
||||
.getRestService()
|
||||
.getBuilder(IsTownhallRoomAvailable.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, examModelId)
|
||||
.call()
|
||||
.getOr(null);
|
||||
|
||||
return townhall != null && townhall.id != null;
|
||||
.getOr(Constants.FALSE_STRING));
|
||||
}
|
||||
|
||||
private PageAction openSearchPopup(final PageAction action) {
|
||||
|
@ -410,9 +451,12 @@ public class MonitoringRunningExam implements TemplateComposer {
|
|||
return action;
|
||||
}
|
||||
|
||||
private PageAction toggleTownhallRoom(final PageAction action) {
|
||||
private PageAction toggleTownhallRoom(
|
||||
final ProctoringGUIService proctoringGUIService,
|
||||
final PageAction action) {
|
||||
|
||||
if (isTownhallRoomActive(action.getEntityKey().modelId)) {
|
||||
closeTownhallRoom(action);
|
||||
closeTownhallRoom(proctoringGUIService, action);
|
||||
this.pageService.firePageEvent(
|
||||
new ActionActivationEvent(
|
||||
true,
|
||||
|
@ -422,7 +466,7 @@ public class MonitoringRunningExam implements TemplateComposer {
|
|||
action.pageContext());
|
||||
return action;
|
||||
} else {
|
||||
openTownhallRoom(action);
|
||||
openTownhallRoom(proctoringGUIService, action);
|
||||
this.pageService.firePageEvent(
|
||||
new ActionActivationEvent(
|
||||
true,
|
||||
|
@ -434,14 +478,13 @@ public class MonitoringRunningExam implements TemplateComposer {
|
|||
}
|
||||
}
|
||||
|
||||
private PageAction openTownhallRoom(final PageAction action) {
|
||||
private PageAction openTownhallRoom(
|
||||
final ProctoringGUIService proctoringGUIService,
|
||||
final PageAction action) {
|
||||
|
||||
try {
|
||||
final EntityKey examId = action.getEntityKey();
|
||||
|
||||
final ProctoringGUIService proctoringGUIService = this.pageService
|
||||
.getCurrentUser()
|
||||
.getProctoringGUIService();
|
||||
|
||||
final String windowName = getTownhallWindowName(examId.modelId);
|
||||
if (!proctoringGUIService.hasWindow(windowName)) {
|
||||
final ProctoringRoomConnection proctoringConnectionData = proctoringGUIService
|
||||
|
@ -475,11 +518,10 @@ public class MonitoringRunningExam implements TemplateComposer {
|
|||
return action;
|
||||
}
|
||||
|
||||
private String getTownhallWindowName(final String examId) {
|
||||
return examId + "_townhall";
|
||||
}
|
||||
private PageAction closeTownhallRoom(
|
||||
final ProctoringGUIService proctoringGUIService,
|
||||
final PageAction action) {
|
||||
|
||||
private PageAction closeTownhallRoom(final PageAction action) {
|
||||
final String examId = action.getEntityKey().modelId;
|
||||
try {
|
||||
|
||||
|
@ -494,34 +536,52 @@ public class MonitoringRunningExam implements TemplateComposer {
|
|||
return action;
|
||||
}
|
||||
|
||||
private void updateTownhallButton(final EntityKey entityKey, final PageContext pageContext) {
|
||||
private void updateTownhallButton(
|
||||
final ProctoringGUIService proctoringGUIService,
|
||||
final PageContext pageContext) {
|
||||
final EntityKey entityKey = pageContext.getEntityKey();
|
||||
|
||||
if (isTownhallRoomActive(entityKey.modelId)) {
|
||||
this.pageService.firePageEvent(
|
||||
new ActionActivationEvent(
|
||||
true,
|
||||
new Tuple<>(
|
||||
ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM,
|
||||
ActionDefinition.MONITOR_EXAM_CLOSE_TOWNHALL_PROCTOR_ROOM)),
|
||||
pageContext);
|
||||
final boolean townhallRoomFromThisUser = proctoringGUIService
|
||||
.isTownhallOpenForUser(entityKey.modelId);
|
||||
if (townhallRoomFromThisUser) {
|
||||
this.pageService.firePageEvent(
|
||||
new ActionActivationEvent(
|
||||
true,
|
||||
new Tuple<>(
|
||||
ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM,
|
||||
ActionDefinition.MONITOR_EXAM_CLOSE_TOWNHALL_PROCTOR_ROOM)),
|
||||
pageContext);
|
||||
} else {
|
||||
this.pageService.firePageEvent(
|
||||
new ActionActivationEvent(
|
||||
false,
|
||||
ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM,
|
||||
ActionDefinition.MONITOR_EXAM_CLOSE_TOWNHALL_PROCTOR_ROOM),
|
||||
pageContext);
|
||||
}
|
||||
} else {
|
||||
this.pageService.firePageEvent(
|
||||
new ActionActivationEvent(
|
||||
true,
|
||||
new Tuple<>(
|
||||
ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM,
|
||||
ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM)),
|
||||
ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM),
|
||||
ActionDefinition.MONITOR_EXAM_OPEN_TOWNHALL_PROCTOR_ROOM,
|
||||
ActionDefinition.MONITOR_EXAM_CLOSE_TOWNHALL_PROCTOR_ROOM),
|
||||
pageContext);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateRoomActions(
|
||||
final EntityKey entityKey,
|
||||
final PageContext pageContext,
|
||||
final Map<String, Pair<RemoteProctoringRoom, TreeItem>> rooms,
|
||||
final PageActionBuilder actionBuilder,
|
||||
final ProctoringServiceSettings proctoringSettings) {
|
||||
final ProctoringServiceSettings proctoringSettings,
|
||||
final ProctoringGUIService proctoringGUIService) {
|
||||
|
||||
updateTownhallButton(entityKey, pageContext);
|
||||
final EntityKey entityKey = pageContext.getEntityKey();
|
||||
updateTownhallButton(proctoringGUIService, pageContext);
|
||||
final I18nSupport i18nSupport = this.pageService.getI18nSupport();
|
||||
this.pageService
|
||||
.getRestService()
|
||||
|
@ -564,7 +624,7 @@ public class MonitoringRunningExam implements TemplateComposer {
|
|||
this.pageService.publishAction(
|
||||
action,
|
||||
_treeItem -> rooms.put(room.name, new Pair<>(room, _treeItem)));
|
||||
addRoomConnectionsPopupListener(entityKey, pageContext, rooms);
|
||||
addRoomConnectionsPopupListener(pageContext, rooms);
|
||||
processProctorRoomActionActivation(rooms.get(room.name).b, room, pageContext);
|
||||
}
|
||||
});
|
||||
|
@ -589,11 +649,11 @@ public class MonitoringRunningExam implements TemplateComposer {
|
|||
}
|
||||
|
||||
private void addRoomConnectionsPopupListener(
|
||||
final EntityKey entityKey,
|
||||
final PageContext pageContext,
|
||||
final Map<String, Pair<RemoteProctoringRoom, TreeItem>> rooms) {
|
||||
|
||||
if (!rooms.isEmpty()) {
|
||||
final EntityKey entityKey = pageContext.getEntityKey();
|
||||
final TreeItem treeItem = rooms.values().iterator().next().b;
|
||||
final Tree tree = treeItem.getParent();
|
||||
if (tree.getData(SHOW_CONNECTION_ACTION_APPLIED) == null) {
|
||||
|
@ -775,4 +835,8 @@ public class MonitoringRunningExam implements TemplateComposer {
|
|||
};
|
||||
}
|
||||
|
||||
private String getTownhallWindowName(final String examId) {
|
||||
return examId + "_townhall";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -84,17 +84,18 @@ public class JitsiMeetProctoringView implements RemoteProctoringView {
|
|||
public void compose(final PageContext pageContext) {
|
||||
|
||||
final ProctoringWindowData proctoringWindowData = ProctoringGUIService.getCurrentProctoringWindowData();
|
||||
|
||||
final Composite parent = pageContext.getParent();
|
||||
|
||||
final Composite content = new Composite(parent, SWT.NONE | SWT.NO_SCROLL);
|
||||
final GridLayout gridLayout = new GridLayout();
|
||||
final ProctoringGUIService proctoringGUIService = this.pageService
|
||||
.getCurrentUser()
|
||||
.getProctoringGUIService();
|
||||
|
||||
content.setLayout(gridLayout);
|
||||
final GridData headerCell = new GridData(SWT.FILL, SWT.FILL, true, true);
|
||||
content.setLayoutData(headerCell);
|
||||
|
||||
parent.addListener(SWT.Dispose, event -> closeRoom(proctoringWindowData));
|
||||
parent.addListener(SWT.Dispose, event -> closeRoom(proctoringGUIService, proctoringWindowData));
|
||||
|
||||
final String url = this.guiServiceInfo
|
||||
.getExternalServerURIBuilder()
|
||||
|
@ -124,7 +125,7 @@ public class JitsiMeetProctoringView implements RemoteProctoringView {
|
|||
|
||||
final Button closeAction = widgetFactory.buttonLocalized(footer, CLOSE_WINDOW_TEXT_KEY);
|
||||
closeAction.setLayoutData(new RowData(150, 30));
|
||||
closeAction.addListener(SWT.Selection, event -> closeRoom(proctoringWindowData));
|
||||
closeAction.addListener(SWT.Selection, event -> closeRoom(proctoringGUIService, proctoringWindowData));
|
||||
|
||||
final BroadcastActionState broadcastActionState = new BroadcastActionState();
|
||||
|
||||
|
@ -232,11 +233,15 @@ public class JitsiMeetProctoringView implements RemoteProctoringView {
|
|||
sendReconfigurationAttributes(examId, roomName, state);
|
||||
}
|
||||
|
||||
private void closeRoom(final ProctoringWindowData proctoringWindowData) {
|
||||
this.pageService
|
||||
.getCurrentUser()
|
||||
.getProctoringGUIService()
|
||||
.closeRoomWindow(proctoringWindowData.windowName);
|
||||
private void closeRoom(
|
||||
final ProctoringGUIService proctoringGUIService,
|
||||
final ProctoringWindowData proctoringWindowData) {
|
||||
|
||||
try {
|
||||
proctoringGUIService.closeRoomWindow(proctoringWindowData.connectionData.roomName);
|
||||
} catch (final Exception e) {
|
||||
log.error("Failed to close proctoring window properly: ", e);
|
||||
}
|
||||
}
|
||||
|
||||
static final class BroadcastActionState {
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright (c) 2021 ETH Zürich, Educational Development and Technology (LET)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session;
|
||||
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class IsTownhallRoomAvailable extends RestCall<String> {
|
||||
|
||||
public IsTownhallRoomAvailable() {
|
||||
super(new TypeKey<>(
|
||||
CallType.GET_SINGLE,
|
||||
EntityType.REMOTE_PROCTORING_ROOM,
|
||||
new TypeReference<String>() {
|
||||
}),
|
||||
HttpMethod.GET,
|
||||
MediaType.APPLICATION_FORM_URLENCODED,
|
||||
API.EXAM_PROCTORING_ENDPOINT
|
||||
+ API.MODEL_ID_VAR_PATH_SEGMENT
|
||||
+ API.EXAM_PROCTORING_TOWNHALL_ROOM_AVAILABLE);
|
||||
}
|
||||
|
||||
}
|
|
@ -50,6 +50,13 @@ public class ProctoringGUIService {
|
|||
this.openWindows.put(windowName, new RoomData(roomName, examId));
|
||||
}
|
||||
|
||||
public boolean isTownhallOpenForUser(final String examId) {
|
||||
return this.openWindows.values().stream()
|
||||
.filter(room -> room.isTownhall && room.examId.equals(examId))
|
||||
.findFirst()
|
||||
.isPresent();
|
||||
}
|
||||
|
||||
public static ProctoringWindowData getCurrentProctoringWindowData() {
|
||||
return (ProctoringWindowData) RWT.getUISession()
|
||||
.getHttpSession()
|
||||
|
@ -105,7 +112,7 @@ public class ProctoringGUIService {
|
|||
.withFormParam(ProctoringRoomConnection.ATTR_SUBJECT, subject)
|
||||
.call()
|
||||
.map(connection -> {
|
||||
this.openWindows.put(windowName, new RoomData(connection.roomName, examId));
|
||||
this.openWindows.put(windowName, new RoomData(connection.roomName, examId, true));
|
||||
return connection;
|
||||
});
|
||||
}
|
||||
|
@ -152,10 +159,16 @@ public class ProctoringGUIService {
|
|||
private static final class RoomData {
|
||||
final String roomName;
|
||||
final String examId;
|
||||
final boolean isTownhall;
|
||||
|
||||
public RoomData(final String roomName, final String examId) {
|
||||
this(roomName, examId, false);
|
||||
}
|
||||
|
||||
public RoomData(final String roomName, final String examId, final boolean townhall) {
|
||||
this.roomName = roomName;
|
||||
this.examId = examId;
|
||||
this.isTownhall = townhall;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -59,6 +59,8 @@ public interface RemoteProctoringRoomDAO {
|
|||
* @return Result refer to the created room record or to an error when happened */
|
||||
Result<RemoteProctoringRoom> createTownhallRoom(Long examId, NewRoom room);
|
||||
|
||||
boolean isTownhallRoomActive(Long examId);
|
||||
|
||||
/** Get the town hall room record for a given exam if existing.
|
||||
*
|
||||
* @param examId the exam identifier
|
||||
|
|
|
@ -20,6 +20,8 @@ import java.util.stream.Collectors;
|
|||
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
@ -42,6 +44,8 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.proctoring.New
|
|||
@WebServiceProfile
|
||||
public class RemoteProctoringRoomDAOImpl implements RemoteProctoringRoomDAO {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(RemoteProctoringRoomDAOImpl.class);
|
||||
|
||||
private static final Object RESERVE_ROOM_LOCK = new Object();
|
||||
|
||||
private final RemoteProctoringRoomRecordMapper remoteProctoringRoomRecordMapper;
|
||||
|
@ -129,15 +133,10 @@ public class RemoteProctoringRoomDAOImpl implements RemoteProctoringRoomDAO {
|
|||
final NewRoom room) {
|
||||
|
||||
return Result.tryCatch(() -> {
|
||||
// check first if town-hall room is not already active
|
||||
final long active = this.remoteProctoringRoomRecordMapper.countByExample()
|
||||
.where(RemoteProctoringRoomRecordDynamicSqlSupport.examId, isEqualTo(examId))
|
||||
.and(RemoteProctoringRoomRecordDynamicSqlSupport.townhallRoom, isNotEqualTo(0))
|
||||
.build()
|
||||
.execute();
|
||||
|
||||
if (active > 0) {
|
||||
throw new IllegalStateException("Townhall, for exam: " + examId + " already existis");
|
||||
// Check first if town-hall room is not already active
|
||||
if (isTownhallRoomActive(examId)) {
|
||||
throw new IllegalStateException("Townhall, for exam: " + examId + " already exists");
|
||||
}
|
||||
|
||||
final RemoteProctoringRoomRecord townhallRoomRecord = new RemoteProctoringRoomRecord(
|
||||
|
@ -159,6 +158,24 @@ public class RemoteProctoringRoomDAOImpl implements RemoteProctoringRoomDAO {
|
|||
.onError(TransactionHandler::rollback);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public boolean isTownhallRoomActive(final Long examId) {
|
||||
try {
|
||||
final long active = this.remoteProctoringRoomRecordMapper.countByExample()
|
||||
.where(RemoteProctoringRoomRecordDynamicSqlSupport.examId, isEqualTo(examId))
|
||||
.and(RemoteProctoringRoomRecordDynamicSqlSupport.townhallRoom, isNotEqualTo(0))
|
||||
.build()
|
||||
.execute();
|
||||
return (active > 0);
|
||||
} catch (final Exception e) {
|
||||
log.error(
|
||||
"Failed to verify town-hall room activity for exam: {}. Mark it as active to avoid double openings",
|
||||
examId, e);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public Result<EntityKey> deleteTownhallRoom(final Long examId) {
|
||||
|
|
|
@ -58,6 +58,8 @@ public interface ExamProctoringRoomService {
|
|||
* @return Result refer to the given exam or to an error when happened */
|
||||
Result<Exam> disposeRoomsForExam(Exam exam);
|
||||
|
||||
boolean isTownhallRoomActive(final Long examId);
|
||||
|
||||
/** This creates a town-hall room for a specific exam. The exam must be active and running
|
||||
* and there must be no other town-hall room already be active. An unique room name will be
|
||||
* created and returned.
|
||||
|
|
|
@ -318,6 +318,11 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTownhallRoomActive(final Long examId) {
|
||||
return this.remoteProctoringRoomDAO.isTownhallRoomActive(examId);
|
||||
}
|
||||
|
||||
private void closeTownhall(
|
||||
final Long examId,
|
||||
final ProctoringServiceSettings proctoringSettings,
|
||||
|
@ -593,4 +598,5 @@ public class ExamProctoringRoomServiceImpl implements ExamProctoringRoomService
|
|||
true)
|
||||
.onError(error -> log.error("Failed to send join instruction: {}", connectionToken, error));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import javax.servlet.ServletOutputStream;
|
|||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
|
@ -35,6 +36,7 @@ import org.springframework.web.bind.annotation.RequestParam;
|
|||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
|
||||
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
|
||||
|
@ -101,7 +103,7 @@ public class ExamAPI_V1_Controller {
|
|||
|
||||
final POSTMapper mapper = new POSTMapper(formParams, request.getQueryString());
|
||||
|
||||
final String remoteAddr = request.getRemoteAddr();
|
||||
final String remoteAddr = this.getClientAddress(request);
|
||||
final Long institutionId = (instIdRequestParam != null)
|
||||
? instIdRequestParam
|
||||
: mapper.getLong(API.PARAM_INSTITUTION_ID);
|
||||
|
@ -163,7 +165,7 @@ public class ExamAPI_V1_Controller {
|
|||
return CompletableFuture.runAsync(
|
||||
() -> {
|
||||
|
||||
final String remoteAddr = request.getRemoteAddr();
|
||||
final String remoteAddr = this.getClientAddress(request);
|
||||
final Long institutionId = getInstitutionId(principal);
|
||||
|
||||
this.sebClientConnectionService.updateClientConnection(
|
||||
|
@ -193,7 +195,7 @@ public class ExamAPI_V1_Controller {
|
|||
return CompletableFuture.runAsync(
|
||||
() -> {
|
||||
|
||||
final String remoteAddr = request.getRemoteAddr();
|
||||
final String remoteAddr = this.getClientAddress(request);
|
||||
final Long institutionId = getInstitutionId(principal);
|
||||
|
||||
this.sebClientConnectionService.establishClientConnection(
|
||||
|
@ -220,7 +222,7 @@ public class ExamAPI_V1_Controller {
|
|||
return CompletableFuture.runAsync(
|
||||
() -> {
|
||||
|
||||
final String remoteAddr = request.getRemoteAddr();
|
||||
final String remoteAddr = this.getClientAddress(request);
|
||||
final Long institutionId = getInstitutionId(principal);
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
|
@ -406,4 +408,23 @@ public class ExamAPI_V1_Controller {
|
|||
}
|
||||
}
|
||||
|
||||
private String getClientAddress(final HttpServletRequest request) {
|
||||
try {
|
||||
final String ipAddress = request.getHeader("X-FORWARDED-FOR");
|
||||
|
||||
if (ipAddress == null) {
|
||||
return request.getRemoteAddr();
|
||||
}
|
||||
|
||||
if (ipAddress.contains(",")) {
|
||||
return StringUtils.split(ipAddress, Constants.COMMA)[0];
|
||||
}
|
||||
|
||||
return ipAddress;
|
||||
} catch (final Exception e) {
|
||||
log.warn("Failed to verify client IP address: {}", e.getMessage());
|
||||
return request.getHeader("X-FORWARDED-FOR");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -234,6 +234,22 @@ public class ExamProctoringController {
|
|||
.onError(error -> log.error("Failed to close remote proctoring break out room {}", roomName, error));
|
||||
}
|
||||
|
||||
@RequestMapping(
|
||||
path = API.MODEL_ID_VAR_PATH_SEGMENT
|
||||
+ API.EXAM_PROCTORING_TOWNHALL_ROOM_AVAILABLE,
|
||||
method = RequestMethod.GET,
|
||||
produces = MediaType.APPLICATION_JSON_VALUE)
|
||||
public String isTownhallRoomAvialbale(
|
||||
@RequestParam(
|
||||
name = API.PARAM_INSTITUTION_ID,
|
||||
required = true,
|
||||
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
|
||||
@PathVariable(name = API.PARAM_MODEL_ID) final Long examId) {
|
||||
|
||||
checkExamReadAccess(institutionId);
|
||||
return String.valueOf(!this.examProcotringRoomService.isTownhallRoomActive(examId));
|
||||
}
|
||||
|
||||
@RequestMapping(
|
||||
path = API.MODEL_ID_VAR_PATH_SEGMENT
|
||||
+ API.EXAM_PROCTORING_TOWNHALL_ROOM_DATA,
|
||||
|
@ -246,7 +262,7 @@ public class ExamProctoringController {
|
|||
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
|
||||
@PathVariable(name = API.PARAM_MODEL_ID) final Long examId) {
|
||||
|
||||
checkAccess(institutionId, examId);
|
||||
checkExamReadAccess(institutionId);
|
||||
return this.examProcotringRoomService
|
||||
.getTownhallRoomData(examId)
|
||||
.getOrElse(() -> RemoteProctoringRoom.NULL_ROOM);
|
||||
|
@ -273,6 +289,13 @@ public class ExamProctoringController {
|
|||
.getOrThrow();
|
||||
}
|
||||
|
||||
private void checkExamReadAccess(final Long institutionId) {
|
||||
this.authorization.check(
|
||||
PrivilegeType.READ,
|
||||
EntityType.EXAM,
|
||||
institutionId);
|
||||
}
|
||||
|
||||
private void checkAccess(final Long institutionId, final Long examId) {
|
||||
this.authorization.check(
|
||||
PrivilegeType.READ,
|
||||
|
|
Loading…
Reference in a new issue