diff --git a/openvidu-server/src/main/java/io/openvidu/server/config/OpenviduConfig.java b/openvidu-server/src/main/java/io/openvidu/server/config/OpenviduConfig.java index 82299eb5..cb294e6a 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/config/OpenviduConfig.java +++ b/openvidu-server/src/main/java/io/openvidu/server/config/OpenviduConfig.java @@ -32,6 +32,9 @@ public class OpenviduConfig { @Value("${openvidu.recording.notification}") String openviduRecordingNotification; + @Value("${openvidu.recording.custom-layout}") + String openviduRecordingCustomLayout; + @Value("${openvidu.recording.version}") String openviduRecordingVersion; @@ -68,12 +71,20 @@ public class OpenviduConfig { return this.openviduRecordingPath; } + public void setOpenViduRecordingPath(String recordingPath) { + this.openviduRecordingPath = recordingPath; + } + public boolean getOpenViduRecordingPublicAccess() { return this.openviduRecordingPublicAccess; } + + public String getOpenviduRecordingCustomLayout() { + return this.openviduRecordingCustomLayout; + } - public void setOpenViduRecordingPath(String recordingPath) { - this.openviduRecordingPath = recordingPath; + public void setOpenViduRecordingCustomLayout(String recordingCustomLayout) { + this.openviduRecordingCustomLayout = recordingCustomLayout; } public String getFinalUrl() { diff --git a/openvidu-server/src/main/java/io/openvidu/server/config/SecurityConfig.java b/openvidu-server/src/main/java/io/openvidu/server/config/SecurityConfig.java index 5b8fbe6d..92d37bff 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/config/SecurityConfig.java +++ b/openvidu-server/src/main/java/io/openvidu/server/config/SecurityConfig.java @@ -11,38 +11,40 @@ import org.springframework.security.config.http.SessionCreationPolicy; @Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { - + @Autowired OpenviduConfig openviduConf; - + @Override protected void configure(HttpSecurity http) throws Exception { + + // Security for API REST ExpressionUrlAuthorizationConfigurer.ExpressionInterceptUrlRegistry conf = http.csrf().disable() - .authorizeRequests() - .antMatchers(HttpMethod.POST, "/api/sessions").authenticated() - .antMatchers(HttpMethod.POST, "/api/tokens").authenticated() - .antMatchers(HttpMethod.POST, "/api/recordings/start").authenticated() - .antMatchers(HttpMethod.POST, "/api/recordings/stop").authenticated() - .antMatchers(HttpMethod.GET, "/api/recordings").authenticated() - .antMatchers(HttpMethod.GET, "/api/recordings/**").authenticated() - .antMatchers(HttpMethod.DELETE, "/api/recordings/**").authenticated() - .antMatchers(HttpMethod.GET, "/config/**").authenticated() - .antMatchers("/").authenticated(); - - if (openviduConf.getOpenViduRecordingPublicAccess()) { - conf = conf.antMatchers("/recordings/*").permitAll(); - } else { - conf = conf.antMatchers("/recordings/*").authenticated(); - } - - conf.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) - .and().httpBasic(); + .authorizeRequests().antMatchers(HttpMethod.POST, "/api/sessions").authenticated() + .antMatchers(HttpMethod.POST, "/api/tokens").authenticated() + .antMatchers(HttpMethod.POST, "/api/recordings/start").authenticated() + .antMatchers(HttpMethod.POST, "/api/recordings/stop").authenticated() + .antMatchers(HttpMethod.GET, "/api/recordings").authenticated() + .antMatchers(HttpMethod.GET, "/api/recordings/**").authenticated() + .antMatchers(HttpMethod.DELETE, "/api/recordings/**").authenticated() + .antMatchers(HttpMethod.GET, "/config/**").authenticated().antMatchers("/").authenticated(); + + // Security for layouts + conf.antMatchers("/layouts/*").authenticated(); + + // Security for recorded videos + if (openviduConf.getOpenViduRecordingPublicAccess()) { + conf = conf.antMatchers("/recordings/*").permitAll(); + } else { + conf = conf.antMatchers("/recordings/*").authenticated(); + } + + conf.and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().httpBasic(); } - + @Autowired - public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { - auth.inMemoryAuthentication() - .withUser("OPENVIDUAPP").password(openviduConf.getOpenViduSecret()).roles("ADMIN"); - } + public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception { + auth.inMemoryAuthentication().withUser("OPENVIDUAPP").password(openviduConf.getOpenViduSecret()).roles("ADMIN"); + } } \ No newline at end of file diff --git a/openvidu-server/src/main/java/io/openvidu/server/kurento/core/KurentoSessionManager.java b/openvidu-server/src/main/java/io/openvidu/server/kurento/core/KurentoSessionManager.java index fffd7d88..b184fef8 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/kurento/core/KurentoSessionManager.java +++ b/openvidu-server/src/main/java/io/openvidu/server/kurento/core/KurentoSessionManager.java @@ -237,8 +237,10 @@ public class KurentoSessionManager extends SessionManager { && session.getActivePublishers() == 0) { // Insecure session recording new Thread(() -> { - recordingService.startRecording(session, new RecordingProperties.Builder().name("") - .recordingLayout(session.getSessionProperties().defaultRecordingLayout()).build()); + recordingService.startRecording(session, + new RecordingProperties.Builder().name("") + .recordingLayout(session.getSessionProperties().defaultRecordingLayout()) + .customLayout(session.getSessionProperties().defaultCustomLayout()).build()); }).start(); } diff --git a/openvidu-server/src/main/java/io/openvidu/server/recording/ComposedRecordingService.java b/openvidu-server/src/main/java/io/openvidu/server/recording/ComposedRecordingService.java index ceb38c84..2062c8fa 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/recording/ComposedRecordingService.java +++ b/openvidu-server/src/main/java/io/openvidu/server/recording/ComposedRecordingService.java @@ -47,6 +47,7 @@ import com.github.dockerjava.core.command.PullImageResultCallback; import io.openvidu.client.OpenViduException; import io.openvidu.client.OpenViduException.Code; +import io.openvidu.java.client.RecordingLayout; import io.openvidu.java.client.RecordingProperties; import io.openvidu.server.CommandExecutor; import io.openvidu.server.OpenViduServer; @@ -87,12 +88,11 @@ public class ComposedRecordingService { String shortSessionId = session.getSessionId().substring(session.getSessionId().lastIndexOf('/') + 1, session.getSessionId().length()); String recordingId = this.getFreeRecordingId(session.getSessionId(), shortSessionId); - String secret = openviduConfig.getOpenViduSecret(); if (properties.name() == null || properties.name().isEmpty()) { // No name provided for the recording file properties = new RecordingProperties.Builder().name(recordingId) - .recordingLayout(properties.recordingLayout()).build(); + .recordingLayout(properties.recordingLayout()).customLayout(properties.customLayout()).build(); } Recording recording = new Recording(session.getSessionId(), recordingId, properties); @@ -111,11 +111,9 @@ public class ComposedRecordingService { e.printStackTrace(); } - String location = OpenViduServer.publicUrl.replaceFirst("wss://", ""); - String layoutUrl = properties.recordingLayout().name().toLowerCase().replaceAll("_", "-"); + String layoutUrl = this.getLayoutUrl(recording, shortSessionId); - envs.add("URL=https://OPENVIDUAPP:" + secret + "@" + location + "/#/layout-" + layoutUrl + "/" + shortSessionId - + "/" + secret); + envs.add("URL=" + layoutUrl); envs.add("RESOLUTION=1920x1080"); envs.add("FRAMERATE=30"); envs.add("VIDEO_ID=" + recordingId); @@ -125,8 +123,7 @@ public class ComposedRecordingService { envs.add("RECORDING_JSON=" + recording.toJson().toJSONString()); log.info(recording.toJson().toJSONString()); - log.debug("Recorder connecting to url {}", - "https://OPENVIDUAPP:" + secret + "@localhost:8443/#/layout-best-fit/" + shortSessionId + "/" + secret); + log.debug("Recorder connecting to url {}", layoutUrl); String containerId = this.runRecordingContainer(envs, "recording_" + recordingId); @@ -456,6 +453,27 @@ public class ComposedRecordingService { throw e; } + private String getLayoutUrl(Recording recording, String shortSessionId) { + String secret = openviduConfig.getOpenViduSecret(); + String location = OpenViduServer.publicUrl.replaceFirst("wss://", ""); + String layout, finalUrl; + + if (RecordingLayout.CUSTOM.equals(recording.getLayout())) { + layout = recording.getCustomLayout(); + layout = layout.startsWith("/") ? layout.substring(1) : layout; + layout = layout.endsWith("/") ? layout.substring(0, layout.length() - 1) : layout; + layout += "/index.html"; + finalUrl = "https://OPENVIDUAPP:" + secret + "@" + location + "/layouts/custom/" + layout + "/?sessionId=" + + shortSessionId + "&secret=" + secret; + } else { + layout = recording.getLayout().name().toLowerCase().replaceAll("_", "-"); + finalUrl = "https://OPENVIDUAPP:" + secret + "@" + location + "/#/layout-" + layout + "/" + shortSessionId + + "/" + secret; + } + + return finalUrl; + } + public void setRecordingVersion(String version) { this.IMAGE_TAG = version; } diff --git a/openvidu-server/src/main/java/io/openvidu/server/recording/LayoutsHttpHandler.java b/openvidu-server/src/main/java/io/openvidu/server/recording/LayoutsHttpHandler.java new file mode 100644 index 00000000..e1b30bad --- /dev/null +++ b/openvidu-server/src/main/java/io/openvidu/server/recording/LayoutsHttpHandler.java @@ -0,0 +1,27 @@ +package io.openvidu.server.recording; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; + +import io.openvidu.server.config.OpenviduConfig; + +@Configuration +public class LayoutsHttpHandler extends WebMvcConfigurerAdapter { + + @Autowired + OpenviduConfig openviduConfig; + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + + String customLayoutsPath = openviduConfig.getOpenviduRecordingCustomLayout(); + customLayoutsPath = customLayoutsPath.endsWith("/") ? customLayoutsPath : customLayoutsPath + "/"; + + openviduConfig.setOpenViduRecordingCustomLayout(customLayoutsPath); + + registry.addResourceHandler("/layouts/custom/**").addResourceLocations("file:" + customLayoutsPath); + } + +} \ No newline at end of file diff --git a/openvidu-server/src/main/java/io/openvidu/server/recording/Recording.java b/openvidu-server/src/main/java/io/openvidu/server/recording/Recording.java index e49770a1..5dc9c4bb 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/recording/Recording.java +++ b/openvidu-server/src/main/java/io/openvidu/server/recording/Recording.java @@ -66,21 +66,17 @@ public class Recording { this.id = id; } - public String getName() { - return this.recordingProperties.name(); - } - - public String setName() { - return this.recordingProperties.name(); - } + public String getName() { + return this.recordingProperties.name(); + } - public RecordingLayout getLayout() { - return this.recordingProperties.recordingLayout(); - } - - public RecordingLayout setLayout() { - return this.recordingProperties.recordingLayout(); - } + public RecordingLayout getLayout() { + return this.recordingProperties.recordingLayout(); + } + + public String getCustomLayout() { + return this.recordingProperties.customLayout(); + } public String getSessionId() { return sessionId; diff --git a/openvidu-server/src/main/java/io/openvidu/server/rest/SessionRestController.java b/openvidu-server/src/main/java/io/openvidu/server/rest/SessionRestController.java index 8903f299..3b103bee 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/rest/SessionRestController.java +++ b/openvidu-server/src/main/java/io/openvidu/server/rest/SessionRestController.java @@ -72,23 +72,34 @@ public class SessionRestController { SessionProperties.Builder builder = new SessionProperties.Builder(); if (params != null) { + String mediaModeString = (String) params.get("mediaMode"); String recordingModeString = (String) params.get("recordingMode"); String defaultRecordingLayoutString = (String) params.get("defaultRecordingLayout"); - String mediaModeString = (String) params.get("mediaMode"); + String defaultCustomLayout = (String) params.get("defaultCustomLayout"); try { + + // Safe parameter retrieval. Default values if not defined if (recordingModeString != null) { RecordingMode recordingMode = RecordingMode.valueOf(recordingModeString); builder = builder.recordingMode(recordingMode); + } else { + builder = builder.recordingMode(RecordingMode.MANUAL); } if (defaultRecordingLayoutString != null) { RecordingLayout defaultRecordingLayout = RecordingLayout.valueOf(defaultRecordingLayoutString); builder = builder.defaultRecordingLayout(defaultRecordingLayout); + } else { + builder.defaultRecordingLayout(RecordingLayout.BEST_FIT); } if (mediaModeString != null) { MediaMode mediaMode = MediaMode.valueOf(mediaModeString); builder = builder.mediaMode(mediaMode); + } else { + builder = builder.mediaMode(MediaMode.ROUTED); } + builder = builder.defaultCustomLayout((defaultCustomLayout != null) ? defaultCustomLayout : ""); + } catch (IllegalArgumentException e) { return this.generateErrorResponse("RecordingMode " + params.get("recordingMode") + " | " + "Default RecordingLayout " + params.get("defaultRecordingLayout") + " | " + "MediaMode " @@ -120,9 +131,7 @@ public class SessionRestController { role = ParticipantRole.PUBLISHER; } - if (metadata == null) { - metadata = ""; - } + metadata = (metadata != null) ? metadata : ""; String token = sessionManager.newToken(sessionId, role, metadata); JSONObject responseJson = new JSONObject(); @@ -149,6 +158,7 @@ public class SessionRestController { String sessionId = (String) params.get("session"); String name = (String) params.get("name"); String recordingLayoutString = (String) params.get("recordingLayout"); + String customLayout = (String) params.get("customLayout"); if (sessionId == null) { // "session" parameter not found @@ -180,9 +190,11 @@ public class SessionRestController { } else { recordingLayout = RecordingLayout.valueOf(recordingLayoutString); } + + customLayout = (customLayout == null) ? session.getSessionProperties().defaultCustomLayout() : customLayout; Recording startedRecording = this.recordingService.startRecording(session, - new RecordingProperties.Builder().name(name).recordingLayout(recordingLayout).build()); + new RecordingProperties.Builder().name(name).recordingLayout(recordingLayout).customLayout(customLayout).build()); return new ResponseEntity<>(startedRecording.toJson(), HttpStatus.OK); } diff --git a/openvidu-server/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/openvidu-server/src/main/resources/META-INF/additional-spring-configuration-metadata.json index ccd5b5c1..be58f0ef 100644 --- a/openvidu-server/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/openvidu-server/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -39,6 +39,11 @@ "type": "java.lang.String", "description": "Which users will receive a notfication (client events 'recordingStarted' and 'recordingStopped') when recording starts and stops: 'none', 'publisher_moderator', 'all'" }, + { + "name": "openvidu.recording.custom-layout", + "type": "java.lang.String", + "description": "Where should OpenVidu Server look for custom recording layouts" + }, { "name": "openvidu.recording.version", "type": "java.lang.String", diff --git a/openvidu-server/src/main/resources/application.properties b/openvidu-server/src/main/resources/application.properties index 60eabdc1..8ccd6efd 100644 --- a/openvidu-server/src/main/resources/application.properties +++ b/openvidu-server/src/main/resources/application.properties @@ -17,4 +17,5 @@ openvidu.cdr: false openvidu.recording: false openvidu.recording.path: /opt/openvidu/recordings openvidu.recording.public-access: false -openvidu.recording.notification: publisher_moderator \ No newline at end of file +openvidu.recording.notification: publisher_moderator +openvidu.recording.custom-layout: /opt/openvidu/custom-layout \ No newline at end of file diff --git a/openvidu-testapp/src/app/components/test-apirest/test-apirest.component.ts b/openvidu-testapp/src/app/components/test-apirest/test-apirest.component.ts index 7c6e1bd4..3dca9934 100644 --- a/openvidu-testapp/src/app/components/test-apirest/test-apirest.component.ts +++ b/openvidu-testapp/src/app/components/test-apirest/test-apirest.component.ts @@ -29,7 +29,7 @@ export class TestApirestComponent implements OnInit, OnDestroy { recordingModes = ['ALWAYS', 'MANUAL']; selectedRecordingMode = 'MANUAL'; - defaultRecordingLayouts = ['BEST_FIT']; + defaultRecordingLayouts = ['BEST_FIT', 'CUSTOM']; selectedDefaultRecordingLayout = 'BEST_FIT'; mediaModes = ['ROUTED'];