Merge branch 'SEBSERV-262' into development
This commit is contained in:
		
						commit
						45322deea0
					
				
					 8 changed files with 129 additions and 30 deletions
				
			
		|  | @ -12,6 +12,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; | |||
| import com.fasterxml.jackson.annotation.JsonIgnoreProperties; | ||||
| import com.fasterxml.jackson.annotation.JsonProperty; | ||||
| 
 | ||||
| import ch.ethz.seb.sebserver.gbl.model.Domain; | ||||
| import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringServerType; | ||||
| 
 | ||||
| @JsonIgnoreProperties(ignoreUnknown = true) | ||||
|  | @ -66,6 +67,9 @@ public class ProctoringRoomConnection { | |||
|     @JsonProperty(ATTR_USER_NAME) | ||||
|     public final String userName; | ||||
| 
 | ||||
|     @JsonProperty(Domain.REMOTE_PROCTORING_ROOM.ATTR_ROOM_DATA) | ||||
|     public final String additionalRoomData; | ||||
| 
 | ||||
|     @JsonCreator | ||||
|     public ProctoringRoomConnection( | ||||
|             @JsonProperty(ProctoringServiceSettings.ATTR_SERVER_TYPE) final ProctoringServerType proctoringServerType, | ||||
|  | @ -79,7 +83,8 @@ public class ProctoringRoomConnection { | |||
|             @JsonProperty(ATTR_ROOM_KEY) final CharSequence roomKey, | ||||
|             @JsonProperty(ATTR_API_KEY) final CharSequence apiKey, | ||||
|             @JsonProperty(ATTR_MEETING_ID) final String meetingId, | ||||
|             @JsonProperty(ATTR_USER_NAME) final String userName) { | ||||
|             @JsonProperty(ATTR_USER_NAME) final String userName, | ||||
|             @JsonProperty(Domain.REMOTE_PROCTORING_ROOM.ATTR_ROOM_DATA) final String additionalRoomData) { | ||||
| 
 | ||||
|         this.proctoringServerType = proctoringServerType; | ||||
|         this.connectionToken = connectionToken; | ||||
|  | @ -93,6 +98,7 @@ public class ProctoringRoomConnection { | |||
|         this.apiKey = apiKey; | ||||
|         this.meetingId = meetingId; | ||||
|         this.userName = userName; | ||||
|         this.additionalRoomData = additionalRoomData; | ||||
|     } | ||||
| 
 | ||||
|     public ProctoringServerType getProctoringServerType() { | ||||
|  | @ -143,6 +149,10 @@ public class ProctoringRoomConnection { | |||
|         return this.meetingId; | ||||
|     } | ||||
| 
 | ||||
|     public String getAdditionalRoomData() { | ||||
|         return this.additionalRoomData; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public String toString() { | ||||
|         final StringBuilder builder = new StringBuilder(); | ||||
|  |  | |||
|  | @ -282,7 +282,9 @@ public class MonitoringRunningExam implements TemplateComposer { | |||
|                             return CONFIRM_CLOSE_TOWNHALL; | ||||
|                         } | ||||
|                     }) | ||||
|                     .withExec(action -> this.monitoringProctoringService.toggleTownhallRoom(proctoringGUIService, | ||||
|                     .withExec(action -> this.monitoringProctoringService.toggleTownhallRoom( | ||||
|                             proctoringGUIService, | ||||
|                             proctoringSettings, | ||||
|                             action)) | ||||
|                     .noEventPropagation() | ||||
|                     .publish(); | ||||
|  |  | |||
|  | @ -119,6 +119,7 @@ public class MonitoringProctoringService { | |||
| 
 | ||||
|     public PageAction toggleTownhallRoom( | ||||
|             final ProctoringGUIService proctoringGUIService, | ||||
|             final ProctoringServiceSettings proctoringSettings, | ||||
|             final PageAction action) { | ||||
| 
 | ||||
|         if (isTownhallRoomActive(action.getEntityKey().modelId)) { | ||||
|  | @ -135,7 +136,7 @@ public class MonitoringProctoringService { | |||
|             } | ||||
| 
 | ||||
|         } else { | ||||
|             if (openTownhallRoom(proctoringGUIService, action)) { | ||||
|             if (openTownhallRoom(proctoringGUIService, proctoringSettings, action)) { | ||||
|                 this.pageService.firePageEvent( | ||||
|                         new ActionActivationEvent( | ||||
|                                 true, | ||||
|  | @ -245,7 +246,7 @@ public class MonitoringProctoringService { | |||
|                     }); | ||||
|             joinURL = roomData.get("start_url"); | ||||
|         } catch (final Exception e) { | ||||
| 
 | ||||
|             log.error("Failed to get proctoring start URL: ", e); | ||||
|         } | ||||
|         final PageContext pc = pageContext.copy() | ||||
|                 .clearAttributes() | ||||
|  | @ -282,17 +283,17 @@ public class MonitoringProctoringService { | |||
|                 String.valueOf(proctoringSettings.examId), | ||||
|                 proctoringConnectionData); | ||||
| 
 | ||||
|         final String zoomStartLink = extractZoomStartLink(room.additionalRoomData); | ||||
|         if (proctoringSettings.useZoomAppClientForCollectingRoom && | ||||
|                 StringUtils.isNotBlank(extractZoomStartLink(room))) { | ||||
|                 StringUtils.isNotBlank(zoomStartLink)) { | ||||
| 
 | ||||
|             final String startLink = extractZoomStartLink(room); | ||||
|             final String script = String.format( | ||||
|                     getOpenRoomScriptTemplate(), | ||||
|                     room.name, | ||||
|                     800, | ||||
|                     1200, | ||||
|                     room.name, | ||||
|                     startLink, | ||||
|                     zoomStartLink, | ||||
|                     ""); | ||||
| 
 | ||||
|             RWT.getClient() | ||||
|  | @ -326,24 +327,11 @@ public class MonitoringProctoringService { | |||
|                         .call() | ||||
|                         .onError(error -> log.error("Failed to notify proctoring room opened: ", error)); | ||||
|             } | ||||
| 
 | ||||
|         } | ||||
| 
 | ||||
|         return action; | ||||
|     } | ||||
| 
 | ||||
|     private String extractZoomStartLink(final RemoteProctoringRoom room) { | ||||
|         try { | ||||
|             final Map<String, String> data = | ||||
|                     this.jsonMapper.readValue(room.additionalRoomData, new TypeReference<Map<String, String>>() { | ||||
|                     }); | ||||
|             return data.get("start_url"); | ||||
|         } catch (final Exception e) { | ||||
|             log.error("Failed to extract Zoom start link: ", e); | ||||
|             return null; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public PageAction openOneToOneRoom( | ||||
|             final PageAction action, | ||||
|             final ClientConnectionData connectionData, | ||||
|  | @ -394,11 +382,14 @@ public class MonitoringProctoringService { | |||
| 
 | ||||
|     private boolean openTownhallRoom( | ||||
|             final ProctoringGUIService proctoringGUIService, | ||||
|             final ProctoringServiceSettings proctoringSettings, | ||||
|             final PageAction action) { | ||||
| 
 | ||||
|         try { | ||||
|             final EntityKey examId = action.getEntityKey(); | ||||
| 
 | ||||
|             final String endpoint = this.remoteProctoringEndpoint; | ||||
|             final String joinURL = this.guiServiceInfo.getExternalServerURIBuilder().toUriString(); | ||||
|             if (proctoringGUIService.getTownhallWindowName(examId.modelId) == null) { | ||||
|                 final ProctoringRoomConnection proctoringConnectionData = proctoringGUIService | ||||
|                         .openTownhallRoom( | ||||
|  | @ -422,8 +413,8 @@ public class MonitoringProctoringService { | |||
|                     800, | ||||
|                     1200, | ||||
|                     "Town-Hall", | ||||
|                     this.guiServiceInfo.getExternalServerURIBuilder().toUriString(), | ||||
|                     this.remoteProctoringEndpoint); | ||||
|                     joinURL, | ||||
|                     endpoint); | ||||
|             javaScriptExecutor.execute(script); | ||||
| 
 | ||||
|         } catch (final Exception e) { | ||||
|  | @ -520,4 +511,18 @@ public class MonitoringProctoringService { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private String extractZoomStartLink(final String additioalRoomAttributesJson) { | ||||
|         try { | ||||
|             final Map<String, String> data = | ||||
|                     this.jsonMapper.readValue( | ||||
|                             additioalRoomAttributesJson, | ||||
|                             new TypeReference<Map<String, String>>() { | ||||
|                             }); | ||||
|             return data.get("start_url"); | ||||
|         } catch (final Exception e) { | ||||
|             log.error("Failed to extract Zoom start link: ", e); | ||||
|             return null; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -379,7 +379,8 @@ public class JitsiProctoringService implements ExamProctoringService { | |||
|                     null, | ||||
|                     null, | ||||
|                     null, | ||||
|                     clientName); | ||||
|                     clientName, | ||||
|                     null); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -75,6 +75,7 @@ import ch.ethz.seb.sebserver.webservice.servicelayer.dao.RemoteProctoringRoomDAO | |||
| import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamProctoringService; | ||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.session.ExamSessionService; | ||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.session.SEBClientInstructionService; | ||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.proctoring.ZoomRoomRequestResponse.ApplyUserSettingsRequest; | ||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.proctoring.ZoomRoomRequestResponse.CreateMeetingRequest; | ||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.proctoring.ZoomRoomRequestResponse.CreateUserRequest; | ||||
| import ch.ethz.seb.sebserver.webservice.servicelayer.session.impl.proctoring.ZoomRoomRequestResponse.MeetingResponse; | ||||
|  | @ -293,7 +294,8 @@ public class ZoomProctoringService implements ExamProctoringService { | |||
|                     credentials.accessToken, | ||||
|                     credentials.clientId, | ||||
|                     String.valueOf(additionalZoomRoomData.meeting_id), | ||||
|                     this.authorizationService.getUserService().getCurrentUser().getUsername()); | ||||
|                     this.authorizationService.getUserService().getCurrentUser().getUsername(), | ||||
|                     remoteProctoringRoom.additionalRoomData); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|  | @ -353,7 +355,8 @@ public class ZoomProctoringService implements ExamProctoringService { | |||
|                     credentials.accessToken, | ||||
|                     credentials.clientId, | ||||
|                     String.valueOf(additionalZoomRoomData.meeting_id), | ||||
|                     clientConnection.clientConnection.userSessionId); | ||||
|                     clientConnection.clientConnection.userSessionId, | ||||
|                     remoteProctoringRoom.additionalRoomData); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|  | @ -553,6 +556,11 @@ public class ZoomProctoringService implements ExamProctoringService { | |||
|                     createUser.getBody(), | ||||
|                     UserResponse.class); | ||||
| 
 | ||||
|             this.zoomRestTemplate.applyUserSettings( | ||||
|                     proctoringSettings.serverURL, | ||||
|                     credentials, | ||||
|                     userResponse.id); | ||||
| 
 | ||||
|             // Then create new meeting with the ad-hoc user/host | ||||
|             final CharSequence meetingPwd = UUID.randomUUID().toString().subSequence(0, 9); | ||||
|             final ResponseEntity<String> createMeeting = this.zoomRestTemplate.createMeeting( | ||||
|  | @ -745,7 +753,6 @@ public class ZoomProctoringService implements ExamProctoringService { | |||
| 
 | ||||
|     private long forExam(final ProctoringServiceSettings examProctoring) { | ||||
| 
 | ||||
|         // TODO | ||||
|         // NOTE: following is the original code that includes the exam end time but seems to make trouble for OLAT | ||||
|         final long nowInSeconds = Utils.getSecondsNow(); | ||||
|         final long nowPlus30MinInSeconds = nowInSeconds + Utils.toSeconds(30 * Constants.MINUTE_IN_MILLIS); | ||||
|  | @ -779,6 +786,7 @@ public class ZoomProctoringService implements ExamProctoringService { | |||
|         private static final int LIZENSED_USER = 2; | ||||
|         private static final String API_TEST_ENDPOINT = "v2/users"; | ||||
|         private static final String API_CREATE_USER_ENDPOINT = "v2/users"; | ||||
|         private static final String API_APPLY_USER_SETTINGS_ENDPOINT = "v2/users/{userId}/settings"; | ||||
|         private static final String API_DELETE_USER_ENDPOINT = "v2/users/{userid}?action=delete"; | ||||
|         private static final String API_USER_CUST_CREATE = "custCreate"; | ||||
|         private static final String API_ZOOM_ROOM_USER = "SEBProctoringRoomUser"; | ||||
|  | @ -856,6 +864,31 @@ public class ZoomProctoringService implements ExamProctoringService { | |||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public ResponseEntity<String> applyUserSettings( | ||||
|                 final String zoomServerUrl, | ||||
|                 final ClientCredentials credentials, | ||||
|                 final String userId) { | ||||
|             try { | ||||
|                 final String url = UriComponentsBuilder | ||||
|                         .fromUriString(zoomServerUrl) | ||||
|                         .path(API_APPLY_USER_SETTINGS_ENDPOINT) | ||||
|                         .buildAndExpand(userId) | ||||
|                         .normalize() | ||||
|                         .toUriString(); | ||||
| 
 | ||||
|                 final ApplyUserSettingsRequest applySettingsRequest = new ApplyUserSettingsRequest(); | ||||
|                 final String body = this.zoomProctoringService.jsonMapper.writeValueAsString(applySettingsRequest); | ||||
|                 final HttpHeaders headers = getHeaders(credentials); | ||||
| 
 | ||||
|                 headers.set(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE); | ||||
|                 final ResponseEntity<String> exchange = exchange(url, HttpMethod.PATCH, body, headers); | ||||
|                 return exchange; | ||||
|             } catch (final Exception e) { | ||||
|                 log.error("Failed to apply user settings for Zoom user: {}", userId, e); | ||||
|                 return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         public ResponseEntity<String> createMeeting( | ||||
|                 final String zoomServerUrl, | ||||
|                 final ClientCredentials credentials, | ||||
|  |  | |||
|  | @ -87,6 +87,50 @@ public interface ZoomRoomRequestResponse { | |||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @JsonIgnoreProperties(ignoreUnknown = true) | ||||
|     static class ApplyUserSettingsRequest { | ||||
|         @JsonProperty final InMeetingSettings in_meeting; | ||||
| 
 | ||||
|         // NOTE: This seems to need a special Zoom Account Plan to work | ||||
|         //@JsonProperty final FeaturesSettings feature; | ||||
| 
 | ||||
|         public ApplyUserSettingsRequest() { | ||||
|             this.in_meeting = new InMeetingSettings(true, 3, 3); | ||||
|             //this.feature = new FeaturesSettings("Basic"); | ||||
|         } | ||||
| 
 | ||||
|         public ApplyUserSettingsRequest( | ||||
|                 final InMeetingSettings in_meeting, | ||||
|                 final FeaturesSettings feature) { | ||||
|             this.in_meeting = in_meeting; | ||||
|             //this.feature = feature; | ||||
|         } | ||||
| 
 | ||||
|         static class InMeetingSettings { | ||||
| 
 | ||||
|             @JsonProperty final boolean auto_saving_chat; | ||||
|             @JsonProperty final int allow_users_save_chats; | ||||
|             @JsonProperty final int allow_participants_chat_with; | ||||
| 
 | ||||
|             public InMeetingSettings( | ||||
|                     final boolean auto_saving_chat, | ||||
|                     final int allow_users_save_chats, | ||||
|                     final int allow_participants_chat_with) { | ||||
| 
 | ||||
|                 this.auto_saving_chat = auto_saving_chat; | ||||
|                 this.allow_users_save_chats = allow_users_save_chats; | ||||
|                 this.allow_participants_chat_with = allow_participants_chat_with; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         static class FeaturesSettings { | ||||
|             @JsonProperty final String concurrent_meeting; | ||||
|             public FeaturesSettings(final String concurrent_meeting) { | ||||
|                 this.concurrent_meeting = concurrent_meeting; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @JsonIgnoreProperties(ignoreUnknown = true) | ||||
|     static class UserResponse { | ||||
|         final String id; | ||||
|  |  | |||
|  | @ -41,7 +41,8 @@ public class JitsiWindowScriptResolverTest { | |||
|                         "API_KEY", | ||||
|                         "ROOM_KEY", | ||||
|                         "MEETING_ID", | ||||
|                         "USER_NAME")); | ||||
|                         "USER_NAME", | ||||
|                         null)); | ||||
| 
 | ||||
|         final ProctoringWindowData proctoringWindowDataOther = new ProctoringWindowData( | ||||
|                 "0", | ||||
|  | @ -58,7 +59,8 @@ public class JitsiWindowScriptResolverTest { | |||
|                         "API_KEY", | ||||
|                         "ROOM_KEY", | ||||
|                         "MEETING_ID", | ||||
|                         "USER_NAME")); | ||||
|                         "USER_NAME", | ||||
|                         null)); | ||||
| 
 | ||||
|         assertFalse(jitsiWindowScriptResolver.applies(proctoringWindowDataOther)); | ||||
|         assertTrue(jitsiWindowScriptResolver.applies(proctoringWindowData)); | ||||
|  |  | |||
|  | @ -41,7 +41,8 @@ public class ZoomWindowScriptResolverTest { | |||
|                         "API_KEY", | ||||
|                         "ROOM_KEY", | ||||
|                         "MEETING_ID", | ||||
|                         "USER_NAME")); | ||||
|                         "USER_NAME", | ||||
|                         null)); | ||||
| 
 | ||||
|         final ProctoringWindowData proctoringWindowDataOther = new ProctoringWindowData( | ||||
|                 "0", | ||||
|  | @ -58,7 +59,8 @@ public class ZoomWindowScriptResolverTest { | |||
|                         "API_KEY", | ||||
|                         "ROOM_KEY", | ||||
|                         "MEETING_ID", | ||||
|                         "USER_NAME")); | ||||
|                         "USER_NAME", | ||||
|                         null)); | ||||
| 
 | ||||
|         assertFalse(zoomWindowScriptResolver.applies(proctoringWindowDataOther)); | ||||
|         assertTrue(zoomWindowScriptResolver.applies(proctoringWindowDataZoom)); | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue
	
	 anhefti
						anhefti