diff --git a/openvidu-server/docker/openvidu-docker-compose/docker-compose.yml b/openvidu-server/docker/openvidu-docker-compose/docker-compose.yml index 013c45d5..969dd9d6 100644 --- a/openvidu-server/docker/openvidu-docker-compose/docker-compose.yml +++ b/openvidu-server/docker/openvidu-docker-compose/docker-compose.yml @@ -20,7 +20,7 @@ services: - OPENVIDU_PUBLICURL= - OPENVIDU_RECORDING=true - OPENVIDU_RECORDING_PATH=${OPENVIDU_RECORDING_FOLDER} - - KMS_URIS="[\"ws://127.0.0.1:8888/kurento\"]" + - KMS_URIS=["ws://localhost:8888/kurento"] - COTURN_IP=${OPENVIDU_DOMAIN_OR_PUBLIC_IP} - COTURN_REDIS_IP=127.0.0.1 - LOGGING_LEVEL_ROOT=${OV_CE_DEBUG_LEVEL:-INFO} @@ -29,7 +29,8 @@ services: image: kurento/${KMS_IMAGE:-kurento-media-server-dev:6.13} restart: on-failure network_mode: host - ulimits: -1 + ulimits: + core: -1 environment: - KMS_EXTERNAL_ADDRESS=auto - KMS_MIN_PORT=40000 diff --git a/openvidu-server/docker/openvidu-docker-compose/readme.md b/openvidu-server/docker/openvidu-docker-compose/readme.md index e5f98ee4..3b5e5c7f 100644 --- a/openvidu-server/docker/openvidu-docker-compose/readme.md +++ b/openvidu-server/docker/openvidu-docker-compose/readme.md @@ -31,6 +31,8 @@ Open ports: - 57001 - 65535 UDP: Used by TURN Server to stablish media connections. - 57001 - 65535 TCP: Used by TURN Server to stablish media connections. +It is important to have a **domain name** pointing to the machine where you are are going to deploy OpenVidu. The platform is deployed using https because is mandatory to use WebRTC. Then, if you do not have a domain name, an ugly warning will appear to your users when enter to your site. And, of course, you can suffer a man in the middle attack. You don't need a valid SSL certificate as one can be created by Let's Encrypt in the installation process. + ## 2. Deployment Instructions ### Clone Repository @@ -48,9 +50,11 @@ $ cd openvidu-server/docker/openvidu-docker-compose OpenVidu configuration is specified in the `.env` file with environment variables. -**YOU MUST** specify the **domain or public IP** of the machine and the OpenVidu **secret**. +**YOU MUST** specify the **DOMAIN_OR_PUBLIC_IP** of the machine and the **OPENVIDU_SECRET**. -All other values have sane defaults. +If you have a domain name, generate a certificate with Let's Encrypt or put your own certificate. + +All other config properties have sane defaults. ``` # OpenVidu configuration @@ -104,7 +108,7 @@ services: - OPENVIDU_SECRET=${OPENVIDU_SECRET} ``` -You can disable it deleting the file `docker-compose.override.yml` (or renaming it in case you want to enable again in the future). +You can disable OpenVidu Call application deleting the file `docker-compose.override.yml` (or renaming it in case you want to enable again in the future). You can configure other dockerized application if you want updating the content of `docker-compose.override.yml` with the following requirements: * You have to bind your application port to 5442 in the host, as this port is used by NGINX to publish your app in port 443. @@ -130,12 +134,18 @@ Creating openvidu-docker-compose_redis_1 ... done Creating openvidu-docker-compose_openvidu-server_1 ... done ``` -Then, you should check openvidu-server logs to verify if all is configured and working as expected with the following command: +Then, you should check openvidu-server logs to verify if all is configured and working as expected. Use the following command: ``` $ docker-compose logs -f openvidu-server ``` +For your convenience, you can execute the following script to perform these two commands (and stop previously started OpenVidu platform, just in case) + +``` +$ ./openvidu-restart.sh +``` + When OpenVidu Platform is ready you will see this message: ``` ---------------------------------------------------- @@ -173,9 +183,9 @@ To change the configuration follow this steps: > TODO: Review that changing domain name with CERTIFICATE_TYPE=letsencrypt regenerates the certificate. -### What to do if OpenVidu is not working +## 3. What to do if OpenVidu is not working -#### Show service logs +### Show service logs Take a look to service logs to see what happen. First, see openvidu-server logs: @@ -196,14 +206,36 @@ $ docker-compose logs -f coturn $ docker-compose logs -f redis $ docker-compose logs -f app ``` +### Review the configuration -#### Updating the log level of the services +Sometimes, we can have a typo when writing a property name. For this reason, openvidu-server print in the log all the configuration properties you are configured in the file and the default values for all other config properties. In that way, you can double check what openvidu-server *see*. -##### Openvidu Server Level logs -If it was necessary to change the level of the kms logs. In the .en file we go to the section "Openvidu Server Level logs" and change the variable `OV_CE_DEBUG_LEVEL` +If `openvidu-server` detects some error, it will show it in the log. -##### Kurento Media Server Level logs -If it was necessary to change the level of the kms logs. In the .en file we go to the section "Kurento Media Server Level logs" and change the variable `KMS_DEBUG_LEVEL` for more information https://doc-kurento.readthedocs.io/en/stable/features/logging.html +``` + Configuration properties + --------------------- + * CERTIFICATE_TYPE=selfsigned + * OPENVIDU_CDR=false + * OPENVIDU_CDR_PATH=log + * OPENVIDU_DOMAIN_OR_PUBLIC_IP=d + * OPENVIDU_RECORDING=true + * OPENVIDU_RECORDING_AUTOSTOP-TIMEOUT=120 + * OPENVIDU_RECORDING_COMPOSED-URL= -### Use other Kurento Media Server docker image -If is necessaries change the Kurento Media Server image, go to the Kurento Media Server image section in the .env file and change the variable `KMS_IMAGE` with the new image that your want use +... +``` + +### Change log level of the services + +#### Openvidu Server Level logs + +To change the level of `openvidu-server` logs change the property `OV_CE_DEBUG_LEVEL`. + +#### Kurento Media Server Level logs + +To change the level of Kurento Media Server `kms` logs change the property `KMS_DEBUG_LEVEL`. For more information about possible values visit https://doc-kurento.readthedocs.io/en/stable/features/logging.html + +### Change Kurento Media Server docker image + +OpenVidu and Kurento Media Server evolves at different rithm. Sometimes, it is possible that a new KMS is released but OpenVidu is not still updated. In that case, in case you hit a bug and that bug is solved in last KMS version, you can test if updating only KMS is working for you. `KMS_IMAGE` property allows you to specify the new KMS image. diff --git a/openvidu-server/src/main/java/io/openvidu/server/OpenViduServer.java b/openvidu-server/src/main/java/io/openvidu/server/OpenViduServer.java index 7c23e0d5..a5810444 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/OpenViduServer.java +++ b/openvidu-server/src/main/java/io/openvidu/server/OpenViduServer.java @@ -19,8 +19,12 @@ package io.openvidu.server; import java.io.IOException; import java.util.ArrayList; +import java.util.Collections; import java.util.List; +import java.util.Map; +import java.util.concurrent.Semaphore; +import org.bouncycastle.util.Arrays; import org.kurento.jsonrpc.internal.server.config.JsonRpcConfiguration; import org.kurento.jsonrpc.server.JsonRpcConfigurer; import org.kurento.jsonrpc.server.JsonRpcHandlerRegistry; @@ -31,6 +35,7 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.DependsOn; import org.springframework.context.annotation.Import; @@ -41,6 +46,7 @@ import io.openvidu.server.cdr.CDRLoggerFile; import io.openvidu.server.cdr.CallDetailRecord; import io.openvidu.server.config.HttpHandshakeInterceptor; import io.openvidu.server.config.OpenviduConfig; +import io.openvidu.server.config.OpenviduConfig.Error; import io.openvidu.server.core.SessionEventsHandler; import io.openvidu.server.core.SessionManager; import io.openvidu.server.core.TokenGenerator; @@ -83,7 +89,7 @@ public class OpenViduServer implements JsonRpcConfigurer { public static String publicurlType; public static String wsUrl; public static String httpUrl; - + @Autowired OpenviduConfig config; @@ -214,69 +220,82 @@ public class OpenViduServer implements JsonRpcConfigurer { } public static void main(String[] args) throws Exception { + + checkConfigProperties(); + log.info("Using /dev/urandom for secure random generation"); System.setProperty("java.security.egd", "file:/dev/./urandom"); - SpringApplication.run(OpenViduServer.class, args); + SpringApplication.run(OpenViduServer.class, Arrays.append(args, "--spring.main.banner-mode=off")); + + } + + private static void checkConfigProperties() throws InterruptedException { + + ConfigurableApplicationContext app = SpringApplication.run(OpenviduConfig.class, + new String[] { "--spring.main.web-application-type=none" }); + OpenviduConfig config = app.getBean(OpenviduConfig.class); + List errors = config.getConfigErrors(); + + if (!errors.isEmpty()) { + + // @formatter:off + String msg = "\n\n\n" + " Configuration errors\n" + " --------------------\n" + "\n"; + + for (Error error : config.getConfigErrors()) { + + msg += " * Property " + config.getPropertyName(error.getProperty()); + + if (error.getValue() == null || error.getValue().equals("")) { + msg += " is not set. "; + } else { + msg += "=" + error.getValue() + ". "; + } + + msg += error.getMessage() + "\n"; + } + + msg += "\n" + "\n" + " Fix config errors\n" + " ---------------\n" + "\n" + + " 1) Return to shell pressing Ctrl+C\n" + + " 2) Set correct values in '.env' configuration file\n" + " 3) Restart OpenVidu with:\n" + + "\n" + " $ ./openvidu-restart.sh\n" + "\n"; + // @formatter:on + + log.info(msg); + + // Wait forever + new Semaphore(0).acquire(); + + } else { + + String msg = "\n\n\n" + " Configuration properties\n" + " ----------------------\n" + "\n"; + + Map configProps = config.getConfigProps(); + List configPropNames = new ArrayList<>(config.getUserProperties()); + Collections.sort(configPropNames); + + for(String property : configPropNames) { + String value = configProps.get(property); + msg += " * "+config.getPropertyName(property)+"="+(value == null? "": value)+"\n"; + } + msg += "\n\n"; + + log.info(msg); + } } @EventListener(ApplicationReadyEvent.class) public void whenReady() { - - String startMessage; - - if(!config.getConfErrors().isEmpty()) { - // @formatter:off - startMessage = - "\n\n----------------------------------------------------\n" + - "\n"+ - " Configuration errors\n" + - " --------------------\n" + - "\n"; - - for(String msg : config.getConfErrors()) { - startMessage += " * "+ msg + "\n"; - } - - startMessage += "\n"+ - "\n"+ - " Instructions\n" + - " ------------\n" + - "\n"+ - " 1) Stop OpenVidu services with command:\n" + - "\n"+ - " $ docker-compose down\n"+ - "\n"+ - " 2) Fix configuration errors in .env file.\n" + - "\n"+ - " 3) Start OpenVidu services with command:\n"+ - "\n"+ - " $ docker-compose up -d\n"+ - "\n"+ - "----------------------------------------------------\n"; - // @formatter:on - - - } else { - - String dashboardUrl = httpUrl+"dashboard/"; - - // @formatter:off - startMessage = - "\n\n----------------------------------------------------\n" + - "\n"+ - " OpenVidu Platform is ready!\n" + - " ---------------------------\n" + - "\n"+ - " * OpenVidu Server: " + httpUrl + "\n"+ - "\n"+ - " * OpenVidu Dashboard: " + dashboardUrl + "\n"+ - "\n"+ - "----------------------------------------------------\n"; - // @formatter:on - } - - log.info(startMessage); + String dashboardUrl = httpUrl + "dashboard/"; + + // @formatter:off + String msg = "\n\n----------------------------------------------------\n" + "\n" + + " OpenVidu Platform is ready!\n" + " ---------------------------\n" + "\n" + + " * OpenVidu Server: " + httpUrl + "\n" + "\n" + " * OpenVidu Dashboard: " + dashboardUrl + "\n" + + "\n" + "----------------------------------------------------\n"; + // @formatter:on + + log.info(msg); } } diff --git a/openvidu-server/src/main/java/io/openvidu/server/config/OpenviduBuildConfig.java b/openvidu-server/src/main/java/io/openvidu/server/config/OpenviduBuildConfig.java new file mode 100644 index 00000000..297c87bf --- /dev/null +++ b/openvidu-server/src/main/java/io/openvidu/server/config/OpenviduBuildConfig.java @@ -0,0 +1,41 @@ +/* + * (C) Copyright 2017-2020 OpenVidu (https://openvidu.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package io.openvidu.server.config; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.info.BuildProperties; +import org.springframework.stereotype.Component; + +@Component +public class OpenviduBuildConfig { + + @Autowired + private BuildProperties buildProperties; + + public String getOpenViduServerVersion() { + String v = this.buildProperties.get("version.openvidu.server"); + if (v == null) { + v = this.getVersion(); + } + return v; + } + + public String getVersion() { + return this.buildProperties.getVersion(); + } +} \ No newline at end of file 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 9cdd5415..f4a02464 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 @@ -18,37 +18,21 @@ package io.openvidu.server.config; import java.io.File; -import java.io.IOException; import java.net.Inet6Address; import java.net.MalformedURLException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.net.UnknownHostException; -import java.nio.file.Files; import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; +import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Properties; -import java.util.Set; -import java.util.SortedMap; -import java.util.TreeMap; -import java.util.concurrent.Callable; -import java.util.function.Consumer; -import java.util.stream.Collectors; -import java.util.stream.Stream; import javax.annotation.PostConstruct; -import com.google.gson.Gson; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonParser; - import org.apache.http.Header; import org.apache.http.message.BasicHeader; import org.kurento.jsonrpc.JsonUtils; @@ -56,15 +40,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.info.BuildProperties; -import org.springframework.core.env.AbstractEnvironment; -import org.springframework.core.env.EnumerablePropertySource; import org.springframework.core.env.Environment; -import org.springframework.core.env.PropertySource; -import org.springframework.core.io.FileSystemResource; -import org.springframework.core.io.support.PropertiesLoaderUtils; import org.springframework.stereotype.Component; +import com.google.gson.Gson; +import com.google.gson.JsonArray; +import com.google.gson.JsonSyntaxException; + import io.openvidu.java.client.OpenViduRole; import io.openvidu.server.OpenViduServer; import io.openvidu.server.cdr.CDREventName; @@ -73,140 +55,120 @@ import io.openvidu.server.recording.RecordingNotification; @Component public class OpenviduConfig { + public static class Error { + + private String property; + private String value; + private String message; + + public Error(String property, String value, String message) { + super(); + this.property = property; + this.value = value; + this.message = message; + } + + public String getProperty() { + return property; + } + + public String getValue() { + return value; + } + + public String getMessage() { + return message; + } + } + private static final Logger log = LoggerFactory.getLogger(OpenviduConfig.class); - public static final Set OPENVIDU_STRING_PROPERTIES = new HashSet<>(Arrays.asList("openvidu.secret", - "openvidu.publicurl", "openvidu.recording.path", "openvidu.recording.notification", - "openvidu.recording.custom-layout", "openvidu.recording.composed-url", "openvidu.recording.version", - "openvidu.webhook.endpoint", "openvidu.cdr.path", "coturn.ip", "coturn.redis.ip")); - - public static final Set OPENVIDU_INTEGER_PROPERTIES = new HashSet<>( - Arrays.asList("openvidu.recording.autostop-timeout", "openvidu.streams.video.max-recv-bandwidth", - "openvidu.streams.video.min-recv-bandwidth", "openvidu.streams.video.max-send-bandwidth", - "openvidu.streams.video.min-send-bandwidth")); - - public static final Set OPENVIDU_BOOLEAN_PROPERTIES = new HashSet<>(Arrays.asList("openvidu.cdr", - "openvidu.recording", "openvidu.recording.public-access", "openvidu.webhook")); - - public static final Set OPENVIDU_ARRAY_PROPERTIES = new HashSet<>( - Arrays.asList("kms.uris", "openvidu.webhook.headers", "openvidu.webhook.events")); - - public static final Set OPENVIDU_PROPERTIES = Stream.of(OPENVIDU_STRING_PROPERTIES, - OPENVIDU_INTEGER_PROPERTIES, OPENVIDU_BOOLEAN_PROPERTIES, OPENVIDU_ARRAY_PROPERTIES) - .flatMap(Collection::stream).collect(Collectors.toSet()); - - public static final List OPENVIDU_VALID_PUBLICURL_VALUES = Arrays - .asList(new String[] { "local", "docker" }); - private static final boolean SHOW_PROPERTIES_AS_ENV_VARS = true; - @Value("#{'${spring.config.additional-location:}'.length() > 0 ? '${spring.config.additional-location:}' : \"\"}") - protected String springConfigLocation; + public static final List OPENVIDU_VALID_PUBLICURL_VALUES = Arrays.asList("local", "docker"); - @Autowired - private BuildProperties buildProperties; + private List configErrors = new ArrayList<>(); - @Value("${kms.uris}") - protected String kmsUris; + private Map configProps = new HashMap<>(); - @Value("${openvidu.publicurl}") - protected String openviduPublicUrl; // local, docker, [FINAL_URL] - - @Value("${server.port}") - protected String serverPort; - - @Value("${openvidu.secret}") - protected String openviduSecret; - - @Value("${openvidu.cdr}") - protected boolean openviduCdr; - - @Value("${openvidu.cdr.path}") - protected String openviduCdrPath; - - @Value("${openvidu.recording}") - protected boolean openviduRecording; - - @Value("${openvidu.recording.path}") - protected String openviduRecordingPath; - - @Value("${openvidu.recording.public-access}") - protected boolean openviduRecordingPublicAccess; - - @Value("${openvidu.recording.notification}") - protected RecordingNotification openviduRecordingNotification; - - @Value("${openvidu.recording.custom-layout}") - protected String openviduRecordingCustomLayout; - - @Value("${openvidu.recording.version}") - protected String openviduRecordingVersion; - - @Value("${openvidu.recording.autostop-timeout}") - protected int openviduRecordingAutostopTimeout; - - @Value("${openvidu.recording.composed-url}") - protected String openviduRecordingComposedUrl; - - @Value("${openvidu.webhook}") - protected boolean openviduWebhook; - - @Value("${openvidu.webhook.endpoint}") - protected String openviduWebhookEndpoint; - - @Value("${openvidu.webhook.headers}") - protected String openviduWebhookHeaders; - - @Value("${openvidu.webhook.events}") - protected String openviduWebhookEvents; - - @Value("${openvidu.streams.video.max-recv-bandwidth}") - protected int openviduStreamsVideoMaxRecvBandwidth; - - @Value("${openvidu.streams.video.min-recv-bandwidth}") - protected int openviduStreamsVideoMinRecvBandwidth; - - @Value("${openvidu.streams.video.max-send-bandwidth}") - protected int openviduStreamsVideoMaxSendBandwidth; - - @Value("${openvidu.streams.video.min-send-bandwidth}") - protected int openviduStreamsVideoMinSendBandwidth; - - @Value("${coturn.redis.ip}") - protected String coturnRedisIp; - - @Value("${coturn.redis.dbname}") - protected String coturnRedisDbname; - - @Value("${coturn.redis.password}") - protected String coturnRedisPassword; - - @Value("${coturn.redis.connect-timeout}") - protected String coturnRedisConnectTimeout; - - @Value("#{'${coturn.ip:}'.length() > 0 ? '${coturn.ip:}' : \"\"}") - protected String coturnIp; - - @Value("#{'${spring.profiles.active:}'.length() > 0 ? '${spring.profiles.active:}'.split(',') : \"default\"}") - protected String springProfile; - - public static String finalUrl; - public static List kmsUrisList = new ArrayList<>(); - public static List
webhookHeadersList = new ArrayList<>(); - public static List webhookEventsList = new ArrayList<>(); - - private List confWarnings = new ArrayList<>(); - private List confErrors = new ArrayList<>(); + private List userConfigProps; @Autowired protected Environment env; - public List getConfErrors() { - return confErrors; + @Value("#{'${spring.profiles.active:}'.length() > 0 ? '${spring.profiles.active:}'.split(',') : \"default\"}") + protected String springProfile; + + // Config properties + + private boolean openviduCdr; + + private String openviduCdrPath; + + private boolean openviduRecording; + + private boolean openviduRecordingPublicAccess; + + private Integer openviduRecordingAutostopTimeout; + + private String openviduRecordingPath; + + private RecordingNotification openviduRecordingNotification; + + private String openviduRecordingCustomLayout; + + private String openviduRecordingVersion; + + private Integer openviduStreamsVideoMaxRecvBandwidth; + + private Integer openviduStreamsVideoMinRecvBandwidth; + + private Integer openviduStreamsVideoMaxSendBandwidth; + + private Integer openviduStreamsVideoMinSendBandwidth; + + private String coturnIp; + + private String coturnRedisIp; + + private boolean openviduWebhookEnabled; + + private String openviduWebhookEndpoint; + + private List
webhookHeadersList; + + private List webhookEventsList; + + private List kmsUrisList; + + private String openviduSecret; + + private String openviduPublicUrl; + + private String openviduRecordingComposedUrl; + + private String serverPort; + + private String coturnRedisDbname; + + private String coturnRedisPassword; + + private String coturnRedisConnectTimeout; + + private String certificateType; + + // Derived properties + + public static String finalUrl; + + // Plain config properties getters + + public String getServerPort() { + return this.serverPort; } - public List getConfWarnings() { - return confWarnings; + public String getCoturnDatabaseDbname() { + return this.coturnRedisDbname; } public List getKmsUris() { @@ -217,18 +179,10 @@ public class OpenviduConfig { return this.openviduPublicUrl; } - public String getServerPort() { - return this.serverPort; - } - public String getOpenViduSecret() { return this.openviduSecret; } - public boolean isOpenViduSecret(String secret) { - return secret.equals(this.getOpenViduSecret()); - } - public boolean isCdrEnabled() { return this.openviduCdr; } @@ -261,18 +215,6 @@ public class OpenviduConfig { this.openviduRecordingCustomLayout = recordingCustomLayout; } - public boolean openviduRecordingCustomLayoutChanged(String path) { - return !"/opt/openvidu/custom-layout".equals(path); - } - - public String getFinalUrl() { - return finalUrl; - } - - public void setFinalUrl(String finalUrl) { - OpenviduConfig.finalUrl = finalUrl.endsWith("/") ? (finalUrl) : (finalUrl + "/"); - } - public String getOpenViduRecordingVersion() { return this.openviduRecordingVersion; } @@ -281,10 +223,6 @@ public class OpenviduConfig { return this.openviduRecordingAutostopTimeout; } - public String getSpringProfile() { - return springProfile; - } - public int getVideoMaxRecvBandwidth() { return this.openviduStreamsVideoMaxRecvBandwidth; } @@ -305,15 +243,6 @@ public class OpenviduConfig { return this.coturnIp; } - public String getCoturnDatabaseString() { - return "\"ip=" + this.coturnRedisIp + " dbname=" + this.coturnRedisDbname + " password=" - + this.coturnRedisPassword + " connect_timeout=" + this.coturnRedisConnectTimeout + "\""; - } - - public String getCoturnDatabaseDbname() { - return this.coturnRedisDbname; - } - public RecordingNotification getOpenViduRecordingNotification() { return this.openviduRecordingNotification; } @@ -323,7 +252,7 @@ public class OpenviduConfig { } public boolean isWebhookEnabled() { - return this.openviduWebhook; + return this.openviduWebhookEnabled; } public String getOpenViduWebhookEndpoint() { @@ -338,6 +267,20 @@ public class OpenviduConfig { return webhookEventsList; } + // Derived properties methods + + public String getSpringProfile() { + return springProfile; + } + + public String getFinalUrl() { + return finalUrl; + } + + public void setFinalUrl(String finalUrlParam) { + finalUrl = finalUrlParam.endsWith("/") ? (finalUrlParam) : (finalUrlParam + "/"); + } + public OpenViduRole[] getRolesFromRecordingNotification() { OpenViduRole[] roles; switch (this.openviduRecordingNotification) { @@ -359,444 +302,141 @@ public class OpenviduConfig { return roles; } - public String getOpenViduServerVersion() { - String v = this.buildProperties.get("version.openvidu.server"); - if (v == null) { - v = this.getVersion(); - } - return v; + public boolean isOpenViduSecret(String secret) { + return secret.equals(this.getOpenViduSecret()); } - public String getVersion() { - return this.buildProperties.getVersion(); + public String getCoturnDatabaseString() { + return "\"ip=" + this.coturnRedisIp + " dbname=" + this.coturnRedisDbname + " password=" + + this.coturnRedisPassword + " connect_timeout=" + this.coturnRedisConnectTimeout + "\""; } - public String getSpringConfigLocation() { - return this.springConfigLocation; + public boolean openviduRecordingCustomLayoutChanged(String path) { + return !"/opt/openvidu/custom-layout".equals(path); } - public boolean hasExternalizedProperties() { - return !this.springConfigLocation.isEmpty(); + // Properties management methods + + public List getConfigErrors() { + return configErrors; } - public URI checkWebsocketUri(String uri) throws RuntimeException { - try { - if (!uri.startsWith("ws://") || uri.startsWith("wss://")) { - throw new Exception("WebSocket protocol not found"); - } - String parsedUri = uri.replaceAll("^ws://", "http://").replaceAll("^wss://", "https://"); - return new URL(parsedUri).toURI(); - } catch (Exception e) { - throw new RuntimeException( - "URI '" + uri + "' has not a valid WebSocket endpoint format: " + e.getMessage()); - } + public Map getConfigProps() { + return configProps; } - public void checkUrl(String url) throws Exception { - try { - new URL(url).toURI(); - } catch (MalformedURLException | URISyntaxException e) { - throw new Exception("String '" + url + "' has not a valid URL format: " + e.getMessage()); - } + public List getUserProperties() { + return userConfigProps; } - /* - * This method checks all types of internet addresses (IPv4, IPv6 and Domains) - */ - public void checkStringValidInetAddress(String property) throws Exception { - String inetAddress = this.checkString(property); - if (inetAddress != null && !inetAddress.isEmpty()) { - try { - Inet6Address.getByName(inetAddress).getHostAddress(); - } catch (UnknownHostException e) { - throw new Exception("String value: ''" + inetAddress + "' with key: " + getPropertyName(property) - + " is not a valid Internet Address (IP or Domain Name): " + e.getMessage()); - } - } + public String getConfigValue(String property) { + + String value = env.getProperty(property); + + this.configProps.put(property, value); + + return value; } - public void checkStringValidPathFormat(String property) throws Exception { - try { - String stringPath = this.checkString(property); - Paths.get(stringPath); - File f = new File(stringPath); - f.getCanonicalPath(); - f.toURI().toString(); - } catch (Exception e) { - throw new Exception("Property " + getPropertyName(property) - + " must be a string with a valid system path format: " + e.getMessage()); - } - } - - public void checkConfigurationParameters(boolean admitStringified) throws Exception { - - boolean webhookEnabled = this.isWebhookEnabled(); - String webhookEndpoint = this.getOpenViduWebhookEndpoint(); - - checkOpenviduSecret(); - - checkOpenviduPublicurl(); - - checkBoolean("openvidu.cdr", admitStringified); - - checkBoolean("openvidu.recording", admitStringified); - - checkBoolean("openvidu.recording.public-access", admitStringified); - - checkIntegerNonNegative("openvidu.recording.autostop-timeout", admitStringified); - - checkOpenviduRecordingNotification(); - - webhookEnabled = checkBoolean("openvidu.webhook", admitStringified); - - webhookEndpoint = checkString("openvidu.webhook.endpoint"); - - checkIntegerNonNegative("openvidu.streams.video.max-recv-bandwidth", admitStringified); - - checkIntegerNonNegative("openvidu.streams.video.min-recv-bandwidth", admitStringified); - - checkIntegerNonNegative("openvidu.streams.video.max-send-bandwidth", admitStringified); - - checkIntegerNonNegative("openvidu.streams.video.min-send-bandwidth", admitStringified); - - checkKmsUris(admitStringified); - - checkWebHookHandlers(admitStringified); - - checkWebhookEvents(admitStringified); - - checkStringValidPathFormat("openvidu.recording.path"); - - checkStringValidPathFormat("openvidu.recording.custom-layout"); - - checkOpenviduRecordingComposedUrl(); - - checkString("openvidu.recording.version"); - - checkStringValidPathFormat("openvidu.cdr.path"); - - checkStringValidInetAddress("coturn.ip"); - - checkStringValidInetAddress("coturn.redis.ip"); - - if (webhookEnabled && (webhookEndpoint == null || webhookEndpoint.isEmpty())) { - throw new Exception("Property " + getPropertyName("openvidu.webhook") + " set to true requires " - + getPropertyName("openvidu.webhook.endpoint") + " to be defined"); - } - } - - private void checkOpenviduRecordingComposedUrl() throws Exception { - String composedUrl = checkString("openvidu.recording.composed-url"); - try { - if (!composedUrl.isEmpty()) { - checkUrl(composedUrl); - } - } catch (Exception e) { - throw new Exception( - "Property " + getPropertyName("openvidu.recording.composed-url") + " not valid. " + e.getMessage()); - } - } - - private void checkWebhookEvents(boolean admitStringified) throws Exception { - - verifyList(admitStringified, "openvidu.webhook.events", list -> checkWebhookEvents(list)); - } - - private void checkWebHookHandlers(boolean admitStringified) throws Exception { - - verifyList(admitStringified, "openvidu.webhook.headers", list -> checkWebhookHeaders(list)); - } - - private void checkKmsUris(boolean admitStringified) throws Exception { - - verifyList(admitStringified, "kms.uris", list -> kmsUrisStringToList(list)); - } - - private void checkOpenviduRecordingNotification() throws Exception { - String recordingNotif = checkString("openvidu.recording.notification"); - try { - RecordingNotification.valueOf(recordingNotif); - } catch (IllegalArgumentException e) { - throw new Exception( - "Property " + getPropertyName("openvidu.recording.notification") + " has not a valid value ('" - + recordingNotif + "'). Must be one of " + Arrays.asList(RecordingNotification.values())); - } - } - - private void checkOpenviduPublicurl() throws Exception { - - final String property = "openvidu.domain.or.public.ip"; - - String domain = checkString(property); - - if (domain != null && !domain.isEmpty()) { - - this.openviduPublicUrl = "https://"+domain; - + public String getPropertyName(String propertyName) { + if (SHOW_PROPERTIES_AS_ENV_VARS) { + return propertyName.replace('.', '_').toUpperCase(); } else { - - final String urlProperty = "openvidu.publicurl"; - - String publicurl = checkString(urlProperty); - - if (publicurl == null || publicurl.isEmpty()) { - - error(getPropertyName(property) + " property cannot be empty"); - - } else { - - if (!OPENVIDU_VALID_PUBLICURL_VALUES.contains(publicurl)) { - try { - checkUrl(publicurl); - } catch (Exception e) { - throw new Exception("Property " + getPropertyName(property) + " not valid. " + e.getMessage()); - } - } - } + return propertyName; } } - private void checkOpenviduSecret() throws Exception { - String secret = checkString("openvidu.secret"); - if (secret.isEmpty()) { - throw new Exception("Property " + getPropertyName("openvidu.secret") + " cannot be empty"); - } - } + private void addError(String property, String msg) { - public String checkString(String property) throws Exception { - try { - String stringValue = env.getProperty(property); - return stringValue; - } catch (ClassCastException e) { - throw new Exception("Property " + getPropertyName(property) + " must be a string: " + e.getMessage()); - } - } + String value = null; - public boolean checkBoolean(String property, boolean admitStringified) throws Exception { - try { - if (admitStringified) { - return Boolean.parseBoolean(env.getProperty(property)); - } else { - throw new Exception("Property " + getPropertyName(property) + " must be a boolean"); - } - } catch (ClassCastException e) { - throw new Exception("Property " + getPropertyName(property) + " must be a boolean: " + e.getMessage()); - } - } - - public Integer checkIntegerNonNegative(String property, boolean admitStringified) throws Exception { - try { - Integer integerValue; - if (admitStringified) { - integerValue = Integer.parseInt(env.getProperty(property)); - } else { - throw new Exception("Property " + getPropertyName(property) + " must be an integer"); - } - if (integerValue < 0) { - throw new Exception("Property " + getPropertyName(property) - + " is an integer but cannot be less than 0 (current value: " + integerValue + ")"); - } - return integerValue; - } catch (ClassCastException e) { - throw new Exception("Property " + getPropertyName(property) + " must be an integer: " + e.getMessage()); - } - } - - public List checkStringArray(String property, boolean admitStringified) throws Exception { - List list; - try { - if (admitStringified) { - list = this.stringifiedArrayOfStringToListOfStrings(env.getProperty(property)); - } else { - throw new Exception("Property " + getPropertyName(property) + " must be an array"); - } - return list; - } catch (ClassCastException e) { - throw new Exception("Property " + getPropertyName(property) + " must be an array: " + e.getMessage()); - } - } - - private void verifyList(boolean admitStringified, final String property, Consumer c) throws Exception { - - String webhookHeaders = extractList(admitStringified, property); - try { - c.accept(webhookHeaders); - } catch (Exception e) { - throw new Exception( - "Property " + getPropertyName(property) + " contains a value not valid: " + e.getMessage()); - } - } - - private String extractList(boolean admitStringified, final String property) throws Exception { - - String strList; - try { - // First check if castable to a List - List list = checkStringArray(property, admitStringified); - String elementString; - for (Object element : list) { - try { - // Check every object is a String value - elementString = (String) element; - } catch (ClassCastException e) { - throw new Exception("Property " + getPropertyName(property) + " is an array, but contains a value (" - + element + ") that is not a string: " + e.getMessage()); - } - } - strList = listToQuotedStringifiedArray(list); - } catch (Exception e) { - // If it is not a list, try casting to String - strList = checkString(property); - } - return strList; - } - - public Properties retrieveExternalizedProperties() throws Exception { - - if (this.springConfigLocation.isEmpty()) { - throw new Exception( - "Externalized properties file not found. Path must be set with configuration parameter 'spring.config.additional-location'"); + if (property != null) { + value = getConfigValue(property); } - Properties externalizedProps = null; - FileSystemResource resource = new FileSystemResource(this.springConfigLocation); - try { - externalizedProps = PropertiesLoaderUtils.loadProperties(resource); - log.info("Properties file found at \"{}\". Content: {}", this.springConfigLocation, externalizedProps); - } catch (IOException e) { - throw new Exception("Error in 'spring.config.additional-location' system property. Cannot load properties: " - + e.getMessage()); - } - // Check OpenVidu Server write permissions in properties path - if (!Files.isWritable(Paths.get(this.springConfigLocation))) { - log.warn( - "The properties path '{}' set with property 'spring.config.additional-location' is missconfigured. Reason: OpenVidu Server needs write permissions. Try running command \"sudo chmod 777 {}\". If not, OpenVidu won't be able to overwrite preexisting properties", - this.springConfigLocation, this.springConfigLocation); - } else { - log.info("OpenVidu Server has write permissions on properties path {}", this.springConfigLocation); - } - return externalizedProps; - } - - public List stringifiedArrayOfStringToListOfStrings(String json) { - Gson gson = new Gson(); - JsonArray jsonArray = gson.fromJson(json, JsonArray.class); - List list = JsonUtils.toStringList(jsonArray); - if (list.size() == 1 && list.get(0).isEmpty()) { - list = new ArrayList<>(); - } - return list; - } - - public List kmsUrisStringToList(String kmsUris) throws RuntimeException { - - if (kmsUris == null || kmsUris.isEmpty()) { - return new ArrayList<>(); - } - - kmsUris = kmsUris.replaceAll("\\s", ""); // Remove all white spaces - kmsUris = kmsUris.replaceAll("\\\\", ""); // Remove previous escapes - kmsUris = kmsUris.replaceAll("\"", ""); // Remove previous double quotes - kmsUris = kmsUris.replaceFirst("^\\[", "[\\\""); // Escape first char - kmsUris = kmsUris.replaceFirst("\\]$", "\\\"]"); // Escape last char - kmsUris = kmsUris.replaceAll(",", "\\\",\\\""); // Escape middle uris - Gson gson = new Gson(); - JsonArray kmsUrisArray = gson.fromJson(kmsUris, JsonArray.class); - - List list = JsonUtils.toStringList(kmsUrisArray); - if (list.size() == 1 && list.get(0).isEmpty()) { - list = new ArrayList<>(); - } else { - for (String uri : list) { - this.checkWebsocketUri(uri); - } - } - return list; - } - - private void checkWebhookEndpoint(String endpoint) throws Exception { - try { - new URL(endpoint).toURI(); - log.info("OpenVidu Webhook endpoint is {}", endpoint); - } catch (MalformedURLException | URISyntaxException e) { - throw new Exception("Webhook endpoint '" + endpoint + "' is not correct. Malformed URL: " + e.getMessage()); - } - } - - private List
checkWebhookHeaders(String headers) throws RuntimeException { - JsonElement elem = JsonParser.parseString(headers); - JsonArray headersJsonArray = elem.getAsJsonArray(); - List
headerList = new ArrayList<>(); - - for (JsonElement jsonElement : headersJsonArray) { - String headerString = jsonElement.getAsString(); - String[] headerSplit = headerString.split(": ", 2); - if (headerSplit.length != 2) { - throw new RuntimeException("HTTP header '" + headerString - + "' syntax is not correct. Must be 'HEADER_NAME: HEADER_VALUE'. For example: 'Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l'"); - } - String headerName = headerSplit[0]; - String headerValue = headerSplit[1]; - if (headerName.isEmpty()) { - throw new RuntimeException( - "HTTP header '" + headerString + "' syntax is not correct. Header name cannot be empty"); - } - if (headerValue.isEmpty()) { - throw new RuntimeException( - "HTTP header '" + headerString + "' syntax is not correct. Header value cannot be empty"); - } - headerList.add(new BasicHeader(headerName, headerValue)); - } - return headerList; - } - - private List checkWebhookEvents(String events) throws RuntimeException { - JsonElement elem = JsonParser.parseString(events); - JsonArray eventsJsonArray = elem.getAsJsonArray(); - List eventList = new ArrayList<>(); - - for (JsonElement jsonElement : eventsJsonArray) { - String eventString = jsonElement.getAsString(); - try { - CDREventName.valueOf(eventString); - } catch (IllegalArgumentException e) { - throw new RuntimeException("Event '" + eventString + "' does not exist"); - } - eventList.add(CDREventName.valueOf(eventString)); - } - return eventList; - } - - public void checkFinalWebHookConfiguration() throws Exception { - if (this.isWebhookEnabled()) { - if (this.openviduWebhookEndpoint != null) { - this.checkWebhookEndpoint(this.openviduWebhookEndpoint); - } else { - throw new Exception( - "OpenVidu WebHook is enabled but no endpoint was set (property 'openvidu.webhook.endpoint' is not defined)"); - } - webhookHeadersList = this.checkWebhookHeaders(this.openviduWebhookHeaders); - webhookEventsList = this.checkWebhookEvents(this.openviduWebhookEvents); - log.info("OpenVidu Webhook endpoint: {}", this.openviduWebhookEndpoint); - log.info("OpenVidu Webhook headers: {}", this.getOpenViduWebhookHeaders().toString()); - log.info("OpenVidu Webhook events: {}", this.getOpenViduWebhookEvents().toString()); - } + this.configErrors.add(new Error(property, value, msg)); } @PostConstruct - protected void init() { + public void checkConfigurationProperties() { - checkConfProperties(); + try { + this.checkConfigurationParameters(); + } catch (Exception e) { + log.error("Exception checking configuration", e); + addError(null, "Exception checking configuration." + e.getClass().getName() + ":" + e.getMessage()); + } - calculatePublicUrl(); + userConfigProps = new ArrayList<>(configProps.keySet()); + userConfigProps.removeAll(Arrays.asList("coturn.ip", "coturn.redis.ip", "kms.uris", "server.port", + "coturn.redis.dbname", "coturn.redis.password", "coturn.redis.connect-timeout")); + } - setCoturnIp(); + // Properties + + public void checkConfigurationParameters() throws Exception { + + serverPort = getConfigValue("server.port"); + + coturnRedisDbname = getConfigValue("coturn.redis.dbname"); + + coturnRedisPassword = getConfigValue("coturn.redis.password"); + + coturnRedisConnectTimeout = getConfigValue("coturn.redis.connect-timeout"); + + openviduSecret = asNonEmptyString("openvidu.secret"); + + checkOpenviduPublicurl(); + + openviduCdr = asBoolean("openvidu.cdr"); + + openviduCdrPath = asFileSystemPath("openvidu.cdr.path"); + + openviduRecording = asBoolean("openvidu.recording"); + openviduRecordingPublicAccess = asBoolean("openvidu.recording.public-access"); + openviduRecordingAutostopTimeout = asNonNegativeInteger("openvidu.recording.autostop-timeout"); + openviduRecordingPath = asFileSystemPath("openvidu.recording.path"); + openviduRecordingCustomLayout = asFileSystemPath("openvidu.recording.custom-layout"); + openviduRecordingVersion = asNonEmptyString("openvidu.recording.version"); + openviduRecordingComposedUrl = asOptionalURL("openvidu.recording.composed-url"); + checkOpenviduRecordingNotification(); + + openviduStreamsVideoMaxRecvBandwidth = asNonNegativeInteger("openvidu.streams.video.max-recv-bandwidth"); + openviduStreamsVideoMinRecvBandwidth = asNonNegativeInteger("openvidu.streams.video.min-recv-bandwidth"); + openviduStreamsVideoMaxSendBandwidth = asNonNegativeInteger("openvidu.streams.video.max-send-bandwidth"); + openviduStreamsVideoMinSendBandwidth = asNonNegativeInteger("openvidu.streams.video.min-send-bandwidth"); + + kmsUrisList = checkKmsUris(); + + checkCoturnIp(); + + coturnRedisIp = asOptionalInetAddress("coturn.redis.ip"); + + checkWebhook(); + + checkCertificateType(); } - private void setCoturnIp() { + private void checkCertificateType() { + + String property = "certificate.type"; + + certificateType = asNonEmptyString(property); + + if(certificateType != null && !certificateType.isEmpty()) { + List validValues = Arrays.asList("selfsigned", "owncert", "letsencrypt"); + if(!validValues.contains(certificateType)) { + addError(property,"Invalid value '"+certificateType+"'. Valid values are "+validValues); + } + } + } - if (this.coturnIp.isEmpty()) { + private void checkCoturnIp() { + + coturnIp = getConfigValue("coturn.ip"); + + if (coturnIp == null || this.coturnIp.isEmpty()) { try { this.coturnIp = new URL(this.getFinalUrl()).getHost(); @@ -807,6 +447,70 @@ public class OpenviduConfig { } } + + private void checkWebhook() throws Exception { + + openviduWebhookEnabled = asBoolean("openvidu.webhook"); + openviduWebhookEndpoint = asOptionalURL("openvidu.webhook.endpoint"); + webhookHeadersList = checkWebhookHeaders(); + webhookEventsList = getWebhookEvents(); + + if (openviduWebhookEnabled && (openviduWebhookEndpoint == null || openviduWebhookEndpoint.isEmpty())) { + addError("openvidu.webhook.endpoint", + "With " + getPropertyName("openvidu.webhook") + "=true, this property cannot be empty"); + } + } + + private void checkOpenviduRecordingNotification() throws Exception { + + String recordingNotif = asNonEmptyString("openvidu.recording.notification"); + try { + openviduRecordingNotification = RecordingNotification.valueOf(recordingNotif); + } catch (IllegalArgumentException e) { + addError("openvidu.recording.notification", + "Must be one of the values " + Arrays.asList(RecordingNotification.values())); + } + } + + private void checkOpenviduPublicurl() throws Exception { + + final String property = "openvidu.domain.or.public.ip"; + + String domain = getConfigValue(property); + + if (domain != null && !domain.isEmpty()) { + + this.openviduPublicUrl = "https://" + domain; + + } else { + + final String urlProperty = "openvidu.publicurl"; + + String publicurl = getConfigValue(urlProperty); + + if (publicurl == null || publicurl.isEmpty()) { + + addError(property, "Cannot be empty"); + + } else { + + if (!OPENVIDU_VALID_PUBLICURL_VALUES.contains(publicurl)) { + try { + checkUrl(publicurl); + } catch (Exception e) { + addError(property, "Is not a valid URL. " + e.getMessage()); + } + } + + this.openviduPublicUrl = publicurl; + } + } + + if(openviduPublicUrl != null && !openviduPublicUrl.isEmpty()) { + calculatePublicUrl(); + } + } + private void calculatePublicUrl() { String publicUrl = this.getOpenViduPublicUrl(); @@ -855,49 +559,211 @@ public class OpenviduConfig { OpenViduServer.publicurlType = type; } - private void checkConfProperties() { - try { - this.checkConfigurationParameters(true); - } catch (Exception e) { - log.error("Exception checking configuration",e); - error(e.getMessage()); + public List checkKmsUris() { + + String property = "kms.uris"; + + String kmsUris = getConfigValue(property); + + if (kmsUris == null || kmsUris.isEmpty()) { + return Arrays.asList(); } + kmsUris = kmsUris.replaceAll("\\s", ""); // Remove all white spaces + kmsUris = kmsUris.replaceAll("\\\\", ""); // Remove previous escapes + kmsUris = kmsUris.replaceAll("\"", ""); // Remove previous double quotes + kmsUris = kmsUris.replaceFirst("^\\[", "[\\\""); // Escape first char + kmsUris = kmsUris.replaceFirst("\\]$", "\\\"]"); // Escape last char + kmsUris = kmsUris.replaceAll(",", "\\\",\\\""); // Escape middle uris + + List kmsUrisArray = asJsonStringsArray(property); + + for (String uri : kmsUrisArray) { + try { + this.checkWebsocketUri(uri); + } catch (Exception e) { + addError(property, uri + " is not a valid WebSocket URL"); + } + } + + return kmsUrisArray; + } + + private List
checkWebhookHeaders() { + + String property = "openvidu.webhook.headers"; + + List headers = asJsonStringsArray(property); + + List
headerList = new ArrayList<>(); + + for (String header : headers) { + + String[] headerSplit = header.split(": ", 2); + if (headerSplit.length != 2) { + addError(property, "HTTP header '" + header + + "' syntax is not correct. Must be 'HEADER_NAME: HEADER_VALUE'. For example: 'Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l'"); + continue; + } + String headerName = headerSplit[0]; + String headerValue = headerSplit[1]; + if (headerName.isEmpty()) { + addError(property, "HTTP header '" + header + "' syntax is not correct. Header name cannot be empty"); + } + if (headerValue.isEmpty()) { + addError(property, "HTTP header '" + header + "' syntax is not correct. Header value cannot be empty"); + } + headerList.add(new BasicHeader(headerName, headerValue)); + } + return headerList; + } + + private List getWebhookEvents() { + + String property = "openvidu.webhook.events"; + + List events = asJsonStringsArray(property); + + List eventList = new ArrayList<>(); + + for (String event : events) { + try { + eventList.add(CDREventName.valueOf(event)); + } catch (IllegalArgumentException e) { + addError(property, "Event '" + event + "' does not exist"); + } + } + return eventList; + } + + // ------------------------------------------------------- + // Format Checkers + // ------------------------------------------------------- + + private String asOptionalURL(String property) { + + String optionalUrl = getConfigValue(property); try { - kmsUrisList = this.kmsUrisStringToList(this.kmsUris); - this.checkFinalWebHookConfiguration(); + if (!optionalUrl.isEmpty()) { + checkUrl(optionalUrl); + } + return optionalUrl; } catch (Exception e) { - error(e.getMessage()); + addError(property, "Is not a valid URL. " + e.getMessage()); + return null; } } - private void error(String msg) { - log.error(msg); - this.confErrors.add(msg); - } + public String asNonEmptyString(String property) { - private String getPropertyName(String propertyName) { - if (SHOW_PROPERTIES_AS_ENV_VARS) { - return propertyName.replace('.', '_').toUpperCase(); + String stringValue = getConfigValue(property); + + if (stringValue != null && !stringValue.isEmpty()) { + return stringValue; } else { - return propertyName; + addError(property, "Cannot be empty."); + return null; } } -// protected Map getFinalPropertiesInUse() { -// final SortedMap props = new TreeMap<>(); -// for (final PropertySource propertySource : ((AbstractEnvironment) env).getPropertySources()) { -// if (!(propertySource instanceof EnumerablePropertySource)) -// continue; -// for (final String name : ((EnumerablePropertySource) propertySource).getPropertyNames()) -// props.computeIfAbsent(name, propertySource::getProperty); -// } -// return props; -// } + public boolean asBoolean(String property) { - private String listToQuotedStringifiedArray(List list) { - return "[" + list.stream().map(s -> "\"" + s + "\"").collect(Collectors.joining(", ")) + "]"; + String value = getConfigValue(property); + + if (value == null) { + addError(property, "Cannot be empty"); + return false; + } + + if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) { + return Boolean.parseBoolean(value); + } else { + addError(property, "Is not a boolean (true or false)"); + return false; + } + } + + public Integer asNonNegativeInteger(String property) { + try { + Integer integerValue = Integer.parseInt(getConfigValue(property)); + + if (integerValue < 0) { + addError(property, "Is not a non negative integer"); + } + return integerValue; + } catch (NumberFormatException e) { + addError(property, "Is not a non negative integer"); + return 0; + } + } + + /* + * This method checks all types of internet addresses (IPv4, IPv6 and Domains) + */ + public String asOptionalInetAddress(String property) { + String inetAddress = getConfigValue(property); + if (inetAddress != null && !inetAddress.isEmpty()) { + try { + Inet6Address.getByName(inetAddress).getHostAddress(); + } catch (UnknownHostException e) { + addError(property, "Is not a valid Internet Address (IP or Domain Name): " + e.getMessage()); + } + } + return inetAddress; + } + + public String asFileSystemPath(String property) { + try { + String stringPath = this.asNonEmptyString(property); + Paths.get(stringPath); + File f = new File(stringPath); + f.getCanonicalPath(); + f.toURI().toString(); + return stringPath; + } catch (Exception e) { + addError(property, "Is not a valid file system path. " + e.getMessage()); + return null; + } + } + + public List asJsonStringsArray(String property) { + + try { + + Gson gson = new Gson(); + JsonArray jsonArray = gson.fromJson(getConfigValue(property), JsonArray.class); + List list = JsonUtils.toStringList(jsonArray); + if (list.size() == 1 && list.get(0).isEmpty()) { + list = new ArrayList<>(); + } + return list; + + } catch (JsonSyntaxException e) { + addError(property, "Is not a valid strings array in JSON format. " + e.getMessage()); + return Arrays.asList(); + } + } + + public URI checkWebsocketUri(String uri) throws Exception { + try { + if (!uri.startsWith("ws://") || uri.startsWith("wss://")) { + throw new Exception("WebSocket protocol not found"); + } + String parsedUri = uri.replaceAll("^ws://", "http://").replaceAll("^wss://", "https://"); + return new URL(parsedUri).toURI(); + } catch (Exception e) { + throw new RuntimeException( + "URI '" + uri + "' has not a valid WebSocket endpoint format: " + e.getMessage()); + } + } + + public void checkUrl(String url) throws Exception { + try { + new URL(url).toURI(); + } catch (MalformedURLException | URISyntaxException e) { + throw new Exception("String '" + url + "' has not a valid URL format: " + e.getMessage()); + } } } \ No newline at end of file diff --git a/openvidu-server/src/main/java/io/openvidu/server/core/TokenGeneratorDefault.java b/openvidu-server/src/main/java/io/openvidu/server/core/TokenGeneratorDefault.java index 234ea4b6..2836f007 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/core/TokenGeneratorDefault.java +++ b/openvidu-server/src/main/java/io/openvidu/server/core/TokenGeneratorDefault.java @@ -22,6 +22,7 @@ import org.springframework.beans.factory.annotation.Autowired; import io.openvidu.java.client.OpenViduRole; import io.openvidu.server.OpenViduServer; +import io.openvidu.server.config.OpenviduBuildConfig; import io.openvidu.server.config.OpenviduConfig; import io.openvidu.server.coturn.CoturnCredentialsService; import io.openvidu.server.coturn.TurnCredentials; @@ -34,6 +35,9 @@ public class TokenGeneratorDefault implements TokenGenerator { @Autowired protected OpenviduConfig openviduConfig; + + @Autowired + protected OpenviduBuildConfig openviduBuildConfig; @Override public Token generateToken(String sessionId, OpenViduRole role, String serverMetadata, @@ -43,7 +47,7 @@ public class TokenGeneratorDefault implements TokenGenerator { token += "&token=" + IdentifierPrefixes.TOKEN_ID + RandomStringUtils.randomAlphabetic(1).toUpperCase() + RandomStringUtils.randomAlphanumeric(15); token += "&role=" + role.name(); - token += "&version=" + openviduConfig.getOpenViduServerVersion(); + token += "&version=" + openviduBuildConfig.getOpenViduServerVersion(); TurnCredentials turnCredentials = null; if (this.coturnCredentialsService.isCoturnAvailable()) { turnCredentials = coturnCredentialsService.createUser(); diff --git a/openvidu-server/src/main/java/io/openvidu/server/rest/ConfigRestController.java b/openvidu-server/src/main/java/io/openvidu/server/rest/ConfigRestController.java index 5a811d42..cce62417 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/rest/ConfigRestController.java +++ b/openvidu-server/src/main/java/io/openvidu/server/rest/ConfigRestController.java @@ -34,6 +34,7 @@ import com.google.gson.JsonArray; import com.google.gson.JsonObject; import io.openvidu.server.cdr.CDREventName; +import io.openvidu.server.config.OpenviduBuildConfig; import io.openvidu.server.config.OpenviduConfig; /** @@ -49,13 +50,16 @@ public class ConfigRestController { @Autowired private OpenviduConfig openviduConfig; + + @Autowired + private OpenviduBuildConfig openviduBuildConfig; @RequestMapping(value = "/openvidu-version", method = RequestMethod.GET) public String getOpenViduServerVersion() { log.info("REST API: GET /config/openvidu-version"); - return openviduConfig.getOpenViduServerVersion(); + return openviduBuildConfig.getOpenViduServerVersion(); } @RequestMapping(value = "/openvidu-publicurl", method = RequestMethod.GET) @@ -96,7 +100,7 @@ public class ConfigRestController { log.info("REST API: GET /config"); JsonObject json = new JsonObject(); - json.addProperty("version", openviduConfig.getVersion()); + json.addProperty("version", openviduBuildConfig.getVersion()); JsonArray kmsUris = new JsonArray(); openviduConfig.getKmsUris().forEach(uri -> kmsUris.add(uri)); json.add("kmsUris", kmsUris); diff --git a/openvidu-server/src/main/resources/application.properties b/openvidu-server/src/main/resources/application.properties index 2bbb2f9d..5c69cf3a 100644 --- a/openvidu-server/src/main/resources/application.properties +++ b/openvidu-server/src/main/resources/application.properties @@ -9,6 +9,7 @@ server.ssl.key-alias=openvidu-selfsigned logging.level.root=info spring.main.allow-bean-definition-overriding=true +certificate.type=selfsigned kms.uris=["ws://localhost:8888/kurento"] openvidu.publicurl=local