Show error when domain/ip or secret are not set

pull/431/head
micaelgallego 2020-03-29 05:10:50 +02:00
parent 19e76d05da
commit 19ac689e9c
6 changed files with 347 additions and 302 deletions

View File

@ -2,11 +2,11 @@
# ---------------------- # ----------------------
# Documentation: https://openvidu.io/docs/reference-docs/openvidu-server-params/ # Documentation: https://openvidu.io/docs/reference-docs/openvidu-server-params/
# OpenVidu SECRET used for apps and to access to the inspector. Change it. # OpenVidu SECRET used for apps to connect to OpenVidu server and users to access to OpenVidu Dashboard
OPENVIDU_SECRET=MY_SECRET OPENVIDU_SECRET=
# Domain name. If you do not have one, the public IP of the machine. # Domain name. If you do not have one, the public IP of the machine. For example 212.4.34.1 or openvidu.example.com.
DOMAIN_OR_PUBLIC_IP=openvidu.example.com OPENVIDU_DOMAIN_OR_PUBLIC_IP=
# Openvidu Folder Record used for save the openvidu recording videos. Change it # Openvidu Folder Record used for save the openvidu recording videos. Change it
# with the folder you want to use from your host. # with the folder you want to use from your host.

View File

@ -10,5 +10,5 @@ services:
ports: ports:
- "5442:80" - "5442:80"
environment: environment:
- OPENVIDU_URL=https://${DOMAIN_OR_PUBLIC_IP} - OPENVIDU_URL=https://${OPENVIDU_DOMAIN_OR_PUBLIC_IP}
- OPENVIDU_SECRET=${OPENVIDU_SECRET} - OPENVIDU_SECRET=${OPENVIDU_SECRET}

View File

@ -11,15 +11,17 @@ services:
volumes: volumes:
- /var/run/docker.sock:/var/run/docker.sock - /var/run/docker.sock:/var/run/docker.sock
- ${OPENVIDU_RECORDING_FOLDER}:${OPENVIDU_RECORDING_FOLDER} - ${OPENVIDU_RECORDING_FOLDER}:${OPENVIDU_RECORDING_FOLDER}
env_file:
- .env
environment: environment:
- SERVER_SSL_ENABLED=false - SERVER_SSL_ENABLED=false
- SERVER_PORT=5443 - SERVER_PORT=5443
- OPENVIDU_PUBLICURL=https://${DOMAIN_OR_PUBLIC_IP}
- OPENVIDU_SECRET=${OPENVIDU_SECRET} - OPENVIDU_SECRET=${OPENVIDU_SECRET}
- OPENVIDU_PUBLICURL=
- OPENVIDU_RECORDING=true - OPENVIDU_RECORDING=true
- OPENVIDU_RECORDING_PATH=${OPENVIDU_RECORDING_FOLDER} - OPENVIDU_RECORDING_PATH=${OPENVIDU_RECORDING_FOLDER}
- KMS_URIS="[\"ws://127.0.0.1:8888/kurento\"]" - KMS_URIS="[\"ws://127.0.0.1:8888/kurento\"]"
- COTURN_IP=${DOMAIN_OR_PUBLIC_IP} - COTURN_IP=${OPENVIDU_DOMAIN_OR_PUBLIC_IP}
- COTURN_REDIS_IP=127.0.0.1 - COTURN_REDIS_IP=127.0.0.1
- LOGGING_LEVEL_ROOT=${OV_CE_DEBUG_LEVEL:-INFO} - LOGGING_LEVEL_ROOT=${OV_CE_DEBUG_LEVEL:-INFO}
@ -58,6 +60,6 @@ services:
- ./certificates:/etc/letsencrypt - ./certificates:/etc/letsencrypt
- ./owncert:/owncert - ./owncert:/owncert
environment: environment:
- DOMAIN_OR_PUBLIC_IP=${DOMAIN_OR_PUBLIC_IP} - DOMAIN_OR_PUBLIC_IP=${OPENVIDU_DOMAIN_OR_PUBLIC_IP}
- CERTIFICATE_TYPE=${CERTIFICATE_TYPE} - CERTIFICATE_TYPE=${CERTIFICATE_TYPE}
- LETSENCRYPT_EMAIL=${LETSENCRYPT_EMAIL} - LETSENCRYPT_EMAIL=${LETSENCRYPT_EMAIL}

View File

@ -148,9 +148,11 @@ When OpenVidu Platform is ready you will see this message:
---------------------------------------------------- ----------------------------------------------------
``` ```
In case OpenVidu sever founds any problem, it will be shown instead of this message.
You can press `Ctrl+C` to come back to the shell. You can press `Ctrl+C` to come back to the shell.
Then you can open OpenVidu Dashboard to verify if videoconference is working as expected. The user is `OPENVIDUAPP` and the password what you have configured in `.env` file. If all is ok, you can open OpenVidu Dashboard to verify if videoconference is working as expected. The user is `OPENVIDUAPP` and the password what you have configured in `.env` file.
If videoconference application is started, it is available in https://server/ If videoconference application is started, it is available in https://server/
@ -163,7 +165,7 @@ To stop the application exec this command:
### Change configuration ### Change configuration
To change the configuration follow this steps: To change the configuration follow this steps:
* Stop the services: `$ docker-compose stop` * Reset the services: `$ docker-compose down`
* Change configuration in `.env` file * Change configuration in `.env` file
* Start the services: `$ docker-compose up -d` * Start the services: `$ docker-compose up -d`

View File

@ -26,6 +26,7 @@ import org.kurento.jsonrpc.server.JsonRpcConfigurer;
import org.kurento.jsonrpc.server.JsonRpcHandlerRegistry; import org.kurento.jsonrpc.server.JsonRpcHandlerRegistry;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
@ -82,6 +83,9 @@ public class OpenViduServer implements JsonRpcConfigurer {
public static String publicurlType; public static String publicurlType;
public static String wsUrl; public static String wsUrl;
public static String httpUrl; public static String httpUrl;
@Autowired
OpenviduConfig config;
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
@ -217,23 +221,61 @@ public class OpenViduServer implements JsonRpcConfigurer {
@EventListener(ApplicationReadyEvent.class) @EventListener(ApplicationReadyEvent.class)
public void whenReady() { public void whenReady() {
log.info("OpenVidu Server listening for client websocket connections on"
+ (OpenViduServer.publicurlType.isEmpty() ? "" : (" " + OpenViduServer.publicurlType)) + " url "
+ OpenViduServer.wsUrl + WS_PATH);
String dashboardUrl = httpUrl+"dashboard/"; 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
}
String startMessage =
"\n\n----------------------------------------------------\n" +
"\n"+
" OpenVidu Platform is ready!\n" +
" ---------------------------\n" +
"\n"+
" * OpenVidu Server: " + httpUrl + "\n"+
"\n"+
" * OpenVidu Dashboard: " + dashboardUrl + "\n"+
"\n"+
"----------------------------------------------------\n";
log.info(startMessage); log.info(startMessage);
} }

View File

@ -37,6 +37,8 @@ import java.util.Properties;
import java.util.Set; import java.util.Set;
import java.util.SortedMap; import java.util.SortedMap;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.concurrent.Callable;
import java.util.function.Consumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -94,7 +96,9 @@ public class OpenviduConfig {
.flatMap(Collection::stream).collect(Collectors.toSet()); .flatMap(Collection::stream).collect(Collectors.toSet());
public static final List<String> OPENVIDU_VALID_PUBLICURL_VALUES = Arrays public static final List<String> OPENVIDU_VALID_PUBLICURL_VALUES = Arrays
.asList(new String[] { "local", "docker", "" }); .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:}' : \"\"}") @Value("#{'${spring.config.additional-location:}'.length() > 0 ? '${spring.config.additional-location:}' : \"\"}")
protected String springConfigLocation; protected String springConfigLocation;
@ -191,9 +195,20 @@ public class OpenviduConfig {
public static List<Header> webhookHeadersList = new ArrayList<>(); public static List<Header> webhookHeadersList = new ArrayList<>();
public static List<CDREventName> webhookEventsList = new ArrayList<>(); public static List<CDREventName> webhookEventsList = new ArrayList<>();
private List<String> confWarnings = new ArrayList<>();
private List<String> confErrors = new ArrayList<>();
@Autowired @Autowired
protected Environment env; protected Environment env;
public List<String> getConfErrors() {
return confErrors;
}
public List<String> getConfWarnings() {
return confWarnings;
}
public List<String> getKmsUris() { public List<String> getKmsUris() {
return kmsUrisList; return kmsUrisList;
} }
@ -364,7 +379,7 @@ public class OpenviduConfig {
return !this.springConfigLocation.isEmpty(); return !this.springConfigLocation.isEmpty();
} }
public URI checkWebsocketUri(String uri) throws Exception { public URI checkWebsocketUri(String uri) throws RuntimeException {
try { try {
if (!uri.startsWith("ws://") || uri.startsWith("wss://")) { if (!uri.startsWith("ws://") || uri.startsWith("wss://")) {
throw new Exception("WebSocket protocol not found"); throw new Exception("WebSocket protocol not found");
@ -372,7 +387,8 @@ public class OpenviduConfig {
String parsedUri = uri.replaceAll("^ws://", "http://").replaceAll("^wss://", "https://"); String parsedUri = uri.replaceAll("^ws://", "http://").replaceAll("^wss://", "https://");
return new URL(parsedUri).toURI(); return new URL(parsedUri).toURI();
} catch (Exception e) { } catch (Exception e) {
throw new Exception("URI '" + uri + "' has not a valid WebSocket endpoint format: " + e.getMessage()); throw new RuntimeException(
"URI '" + uri + "' has not a valid WebSocket endpoint format: " + e.getMessage());
} }
} }
@ -387,297 +403,252 @@ public class OpenviduConfig {
/* /*
* This method checks all types of internet addresses (IPv4, IPv6 and Domains) * This method checks all types of internet addresses (IPv4, IPv6 and Domains)
*/ */
public void checkStringValidInetAddress(Map<String, ?> parameters, String key) throws Exception { public void checkStringValidInetAddress(String property) throws Exception {
String inetAddress = this.checkString(parameters, key); String inetAddress = this.checkString(property);
if (!inetAddress.isEmpty()) { if (inetAddress != null && !inetAddress.isEmpty()) {
try { try {
Inet6Address.getByName(inetAddress).getHostAddress(); Inet6Address.getByName(inetAddress).getHostAddress();
} catch (UnknownHostException e) { } catch (UnknownHostException e) {
throw new Exception("String value: ''" + inetAddress + "' with key: '" + key throw new Exception("String value: ''" + inetAddress + "' with key: " + getPropertyName(property)
+ "' is not a valid Internet Address (IP or Domain Name): " + e.getMessage()); + " is not a valid Internet Address (IP or Domain Name): " + e.getMessage());
} }
} }
} }
public void checkStringValidPathFormat(Map<String, ?> parameters, String key) throws Exception { public void checkStringValidPathFormat(String property) throws Exception {
try { try {
String stringPath = this.checkString(parameters, key); String stringPath = this.checkString(property);
Paths.get(stringPath); Paths.get(stringPath);
File f = new File(stringPath); File f = new File(stringPath);
f.getCanonicalPath(); f.getCanonicalPath();
f.toURI().toString(); f.toURI().toString();
} catch (Exception e) { } catch (Exception e) {
throw new Exception( throw new Exception("Property " + getPropertyName(property)
"Property '" + key + "' must be a string with a valid system path format: " + e.getMessage()); + " must be a string with a valid system path format: " + e.getMessage());
} }
} }
public Properties checkConfigurationParameters(Map<String, ?> parameters, Collection<String> validKeys, public void checkConfigurationParameters(boolean admitStringified) throws Exception {
boolean admitStringified) throws Exception {
Properties stringifiedProperties = new Properties();
parameters = this.filterValidParameters(parameters, validKeys);
log.info("Checking configuration parameters: {}", parameters.keySet());
boolean webhookEnabled = this.isWebhookEnabled(); boolean webhookEnabled = this.isWebhookEnabled();
String webhookEndpoint = this.getOpenViduWebhookEndpoint(); String webhookEndpoint = this.getOpenViduWebhookEndpoint();
for (String parameter : parameters.keySet()) { checkOpenviduSecret();
switch (parameter) {
case "openvidu.secret":
String secret = checkString(parameters, parameter);
if (secret.isEmpty()) {
throw new Exception("Property 'openvidu.secret' cannot be empty");
}
break;
case "openvidu.publicurl":
String publicurl = checkString(parameters, parameter);
if (!OPENVIDU_VALID_PUBLICURL_VALUES.contains(publicurl)) {
try {
checkUrl(publicurl);
} catch (Exception e) {
throw new Exception("Property 'openvidu.publicurl' not valid. " + e.getMessage());
}
}
break;
case "openvidu.cdr":
checkBoolean(parameters, parameter, admitStringified);
break;
case "openvidu.recording":
checkBoolean(parameters, parameter, admitStringified);
break;
case "openvidu.recording.public-access":
checkBoolean(parameters, parameter, admitStringified);
break;
case "openvidu.recording.autostop-timeout":
checkIntegerNonNegative(parameters, parameter, admitStringified);
break;
case "openvidu.recording.notification":
String recordingNotif = checkString(parameters, parameter);
try {
RecordingNotification.valueOf(recordingNotif);
} catch (IllegalArgumentException e) {
throw new Exception("Property 'openvidu.recording.notification' has not a valid value ('"
+ recordingNotif + "'). Must be one of " + Arrays.asList(RecordingNotification.values()));
}
break;
case "openvidu.webhook":
webhookEnabled = checkBoolean(parameters, parameter, admitStringified);
break;
case "openvidu.webhook.endpoint":
webhookEndpoint = checkString(parameters, parameter);
break;
case "openvidu.streams.video.max-recv-bandwidth":
checkIntegerNonNegative(parameters, parameter, admitStringified);
break;
case "openvidu.streams.video.min-recv-bandwidth":
checkIntegerNonNegative(parameters, parameter, admitStringified);
break;
case "openvidu.streams.video.max-send-bandwidth":
checkIntegerNonNegative(parameters, parameter, admitStringified);
break;
case "openvidu.streams.video.min-send-bandwidth":
checkIntegerNonNegative(parameters, parameter, admitStringified);
break;
case "kms.uris":
String kmsUris;
try {
// First check if castable to a List
List<String> list = checkStringArray(parameters, parameter, 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 'kms.uris' is an array, but contains a value (" + element
+ ") that is not a string: " + e.getMessage());
}
}
kmsUris = list.toString();
} catch (Exception e) {
// If it is not a list, try casting to String
kmsUris = checkString(parameters, parameter);
}
// Finally check all strings have a valid WebSocket URI format
try {
kmsUrisStringToList(kmsUris);
} catch (Exception e) {
throw new Exception(
"Property 'kms.uris' is an array of strings, but contains some value that has not a valid WbeSocket URI format: "
+ e.getMessage());
}
stringifiedProperties.setProperty(parameter, kmsUris);
break;
case "openvidu.webhook.headers":
String webhookHeaders;
try {
// First check if castable to a List
List<String> list = checkStringArray(parameters, parameter, 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 'openvidu.webhook.headers' is an array, but contains a value (" + element
+ ") that is not a string: " + e.getMessage());
}
}
webhookHeaders = listToQuotedStringifiedArray(list);
} catch (Exception e) {
// If it is not a list, try casting to String
webhookHeaders = checkString(parameters, parameter);
}
try {
checkWebhookHeaders(webhookHeaders);
} catch (Exception e) {
throw new Exception(
"Property 'openvidu.webhook.headers' contains a value not valid: " + e.getMessage());
}
stringifiedProperties.setProperty(parameter, webhookHeaders);
break;
case "openvidu.webhook.events":
String webhookEvents;
try {
// First check if castable to a List
List<String> list = checkStringArray(parameters, parameter, 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 'openvidu.webhook.events' is an array, but contains a value ("
+ element + ") that is not a string: " + e.getMessage());
}
}
webhookEvents = listToQuotedStringifiedArray(list);
} catch (Exception e) {
// If it is not a list, try casting to String
webhookEvents = checkString(parameters, parameter);
}
try {
checkWebhookEvents(webhookEvents);
} catch (Exception e) {
throw new Exception(
"Property 'openvidu.webhook.events' contains a value not valid: " + e.getMessage());
}
stringifiedProperties.setProperty(parameter, webhookEvents);
break;
case "openvidu.recording.path":
checkStringValidPathFormat(parameters, parameter);
break;
case "openvidu.recording.custom-layout":
checkStringValidPathFormat(parameters, parameter);
break;
case "openvidu.recording.composed-url":
String composedUrl = checkString(parameters, parameter);
try {
if (!composedUrl.isEmpty()) {
checkUrl(composedUrl);
}
} catch (Exception e) {
throw new Exception("Property 'openvidu.recording.composed-url' not valid. " + e.getMessage());
}
break;
case "openvidu.recording.version":
checkString(parameters, parameter);
break;
case "openvidu.cdr.path":
checkStringValidPathFormat(parameters, parameter);
break;
case "coturn.ip":
checkStringValidInetAddress(parameters, parameter);
break;
case "coturn.redis.ip":
checkStringValidInetAddress(parameters, parameter);
break;
default:
log.warn("Unknown configuration parameter '{}'", parameter);
}
if (!stringifiedProperties.containsKey(parameter)) { checkOpenviduPublicurl();
stringifiedProperties.setProperty(parameter, parameters.get(parameter).toString());
} 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())) { if (webhookEnabled && (webhookEndpoint == null || webhookEndpoint.isEmpty())) {
throw new Exception( throw new Exception("Property " + getPropertyName("openvidu.webhook") + " set to true requires "
"Property 'openvidu.webhook' set to true requires 'openvidu.webhook.endpoint' to be defined"); + getPropertyName("openvidu.webhook.endpoint") + " to be defined");
} }
return stringifiedProperties;
} }
public String checkString(Map<String, ?> parameters, String key) throws Exception { private void checkOpenviduRecordingComposedUrl() throws Exception {
String composedUrl = checkString("openvidu.recording.composed-url");
try { try {
String stringValue = (String) parameters.get(key); 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;
} 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());
}
}
}
}
}
private void checkOpenviduSecret() throws Exception {
String secret = checkString("openvidu.secret");
if (secret.isEmpty()) {
throw new Exception("Property " + getPropertyName("openvidu.secret") + " cannot be empty");
}
}
public String checkString(String property) throws Exception {
try {
String stringValue = env.getProperty(property);
return stringValue; return stringValue;
} catch (ClassCastException e) { } catch (ClassCastException e) {
throw new Exception("Property '" + key + "' must be a string: " + e.getMessage()); throw new Exception("Property " + getPropertyName(property) + " must be a string: " + e.getMessage());
} }
} }
public boolean checkBoolean(Map<String, ?> parameters, String key, boolean admitStringified) throws Exception { public boolean checkBoolean(String property, boolean admitStringified) throws Exception {
try { try {
if (parameters.get(key) instanceof Boolean) { if (admitStringified) {
return (Boolean) parameters.get(key); return Boolean.parseBoolean(env.getProperty(property));
} else if (admitStringified) {
boolean booleanValue = Boolean.parseBoolean((String) parameters.get(key));
return booleanValue;
} else { } else {
throw new Exception("Property '" + key + "' must be a boolean"); throw new Exception("Property " + getPropertyName(property) + " must be a boolean");
} }
} catch (ClassCastException e) { } catch (ClassCastException e) {
throw new Exception("Property '" + key + "' must be a boolean: " + e.getMessage()); throw new Exception("Property " + getPropertyName(property) + " must be a boolean: " + e.getMessage());
} }
} }
public Integer checkIntegerNonNegative(Map<String, ?> parameters, String key, boolean admitStringified) public Integer checkIntegerNonNegative(String property, boolean admitStringified) throws Exception {
throws Exception {
try { try {
Integer integerValue; Integer integerValue;
if (parameters.get(key) instanceof Integer) { if (admitStringified) {
integerValue = (Integer) parameters.get(key); integerValue = Integer.parseInt(env.getProperty(property));
} else if (admitStringified) {
integerValue = Integer.parseInt((String) parameters.get(key));
} else { } else {
throw new Exception("Property '" + key + "' must be an integer"); throw new Exception("Property " + getPropertyName(property) + " must be an integer");
} }
if (integerValue < 0) { if (integerValue < 0) {
throw new Exception("Property '" + key + "' is an integer but cannot be less than 0 (current value: " throw new Exception("Property " + getPropertyName(property)
+ integerValue + ")"); + " is an integer but cannot be less than 0 (current value: " + integerValue + ")");
} }
return integerValue; return integerValue;
} catch (ClassCastException e) { } catch (ClassCastException e) {
throw new Exception("Property '" + key + "' must be an integer: " + e.getMessage()); throw new Exception("Property " + getPropertyName(property) + " must be an integer: " + e.getMessage());
} }
} }
public List<String> checkStringArray(Map<String, ?> parameters, String key, boolean admitStringified) public List<String> checkStringArray(String property, boolean admitStringified) throws Exception {
throws Exception {
List<String> list; List<String> list;
try { try {
if (parameters.get(key) instanceof Collection<?>) { if (admitStringified) {
list = (List<String>) parameters.get(key); list = this.stringifiedArrayOfStringToListOfStrings(env.getProperty(property));
} else if (admitStringified) {
list = this.stringifiedArrayOfStringToListOfStrings((String) parameters.get(key));
} else { } else {
throw new Exception("Property '" + key + "' must be an array"); throw new Exception("Property " + getPropertyName(property) + " must be an array");
} }
return list; return list;
} catch (ClassCastException e) { } catch (ClassCastException e) {
throw new Exception("Property '" + key + "' must be an array: " + e.getMessage()); throw new Exception("Property " + getPropertyName(property) + " must be an array: " + e.getMessage());
} }
} }
public Map<String, ?> filterValidParameters(Map<String, ?> parameters, Collection<String> validKeys) { private void verifyList(boolean admitStringified, final String property, Consumer<String> c) throws Exception {
return parameters.entrySet().stream().filter(x -> validKeys.contains(x.getKey()))
.collect(Collectors.toMap(x -> x.getKey(), x -> x.getValue())); 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<String> 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 { public Properties retrieveExternalizedProperties() throws Exception {
@ -717,7 +688,12 @@ public class OpenviduConfig {
return list; return list;
} }
public List<String> kmsUrisStringToList(String kmsUris) throws Exception { public List<String> kmsUrisStringToList(String kmsUris) throws RuntimeException {
if (kmsUris == null || kmsUris.isEmpty()) {
return new ArrayList<>();
}
kmsUris = kmsUris.replaceAll("\\s", ""); // Remove all white spaces kmsUris = kmsUris.replaceAll("\\s", ""); // Remove all white spaces
kmsUris = kmsUris.replaceAll("\\\\", ""); // Remove previous escapes kmsUris = kmsUris.replaceAll("\\\\", ""); // Remove previous escapes
kmsUris = kmsUris.replaceAll("\"", ""); // Remove previous double quotes kmsUris = kmsUris.replaceAll("\"", ""); // Remove previous double quotes
@ -747,7 +723,7 @@ public class OpenviduConfig {
} }
} }
private List<Header> checkWebhookHeaders(String headers) throws Exception { private List<Header> checkWebhookHeaders(String headers) throws RuntimeException {
JsonElement elem = JsonParser.parseString(headers); JsonElement elem = JsonParser.parseString(headers);
JsonArray headersJsonArray = elem.getAsJsonArray(); JsonArray headersJsonArray = elem.getAsJsonArray();
List<Header> headerList = new ArrayList<>(); List<Header> headerList = new ArrayList<>();
@ -756,17 +732,17 @@ public class OpenviduConfig {
String headerString = jsonElement.getAsString(); String headerString = jsonElement.getAsString();
String[] headerSplit = headerString.split(": ", 2); String[] headerSplit = headerString.split(": ", 2);
if (headerSplit.length != 2) { if (headerSplit.length != 2) {
throw new Exception("HTTP header '" + headerString throw new RuntimeException("HTTP header '" + headerString
+ "' syntax is not correct. Must be 'HEADER_NAME: HEADER_VALUE'. For example: 'Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l'"); + "' syntax is not correct. Must be 'HEADER_NAME: HEADER_VALUE'. For example: 'Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l'");
} }
String headerName = headerSplit[0]; String headerName = headerSplit[0];
String headerValue = headerSplit[1]; String headerValue = headerSplit[1];
if (headerName.isEmpty()) { if (headerName.isEmpty()) {
throw new Exception( throw new RuntimeException(
"HTTP header '" + headerString + "' syntax is not correct. Header name cannot be empty"); "HTTP header '" + headerString + "' syntax is not correct. Header name cannot be empty");
} }
if (headerValue.isEmpty()) { if (headerValue.isEmpty()) {
throw new Exception( throw new RuntimeException(
"HTTP header '" + headerString + "' syntax is not correct. Header value cannot be empty"); "HTTP header '" + headerString + "' syntax is not correct. Header value cannot be empty");
} }
headerList.add(new BasicHeader(headerName, headerValue)); headerList.add(new BasicHeader(headerName, headerValue));
@ -774,7 +750,7 @@ public class OpenviduConfig {
return headerList; return headerList;
} }
private List<CDREventName> checkWebhookEvents(String events) throws Exception { private List<CDREventName> checkWebhookEvents(String events) throws RuntimeException {
JsonElement elem = JsonParser.parseString(events); JsonElement elem = JsonParser.parseString(events);
JsonArray eventsJsonArray = elem.getAsJsonArray(); JsonArray eventsJsonArray = elem.getAsJsonArray();
List<CDREventName> eventList = new ArrayList<>(); List<CDREventName> eventList = new ArrayList<>();
@ -784,7 +760,7 @@ public class OpenviduConfig {
try { try {
CDREventName.valueOf(eventString); CDREventName.valueOf(eventString);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
throw new Exception("Event '" + eventString + "' does not exist"); throw new RuntimeException("Event '" + eventString + "' does not exist");
} }
eventList.add(CDREventName.valueOf(eventString)); eventList.add(CDREventName.valueOf(eventString));
} }
@ -809,28 +785,32 @@ public class OpenviduConfig {
@PostConstruct @PostConstruct
protected void init() { protected void init() {
// Check configuration parameters
Map<String, ?> props = getFinalPropertiesInUse();
try { checkConfProperties();
this.checkConfigurationParameters(props, OPENVIDU_PROPERTIES, true);
} catch (Exception e) {
log.error(e.getMessage());
log.error("Shutting down OpenVidu Server");
System.exit(1);
}
try { calculatePublicUrl();
if (OPENVIDU_PROPERTIES.contains("kms.uris")) {
kmsUrisList = this.kmsUrisStringToList(this.kmsUris); setCoturnIp();
}
private void setCoturnIp() {
if (this.coturnIp.isEmpty()) {
try {
this.coturnIp = new URL(this.getFinalUrl()).getHost();
log.info("Coturn IP: " + coturnIp);
} catch (MalformedURLException e) {
log.error("Can't get Domain name from OpenVidu public Url: " + e.getMessage());
} }
this.checkFinalWebHookConfiguration();
} catch (Exception e) {
log.error("Unexpected exception when setting final value of configuration parameters: {}", e.getMessage());
} }
}
private void calculatePublicUrl() {
// Generate final public url
String publicUrl = this.getOpenViduPublicUrl(); String publicUrl = this.getOpenViduPublicUrl();
String type = ""; String type = "";
switch (publicUrl) { switch (publicUrl) {
case "docker": case "docker":
@ -873,30 +853,49 @@ public class OpenviduConfig {
this.setFinalUrl(finalUrl); this.setFinalUrl(finalUrl);
OpenViduServer.httpUrl = this.getFinalUrl(); OpenViduServer.httpUrl = this.getFinalUrl();
OpenViduServer.publicurlType = type; OpenViduServer.publicurlType = type;
if (this.coturnIp.isEmpty()) {
try {
this.coturnIp = new URL(this.getFinalUrl()).getHost();
log.info("Coturn IP: " + coturnIp);
} catch (MalformedURLException e) {
log.error("Can't get Domain name from OpenVidu public Url: " + e.getMessage());
}
}
} }
protected Map<String, Object> getFinalPropertiesInUse() { private void checkConfProperties() {
final SortedMap<String, Object> props = new TreeMap<>();
for (final PropertySource<?> propertySource : ((AbstractEnvironment) env).getPropertySources()) { try {
if (!(propertySource instanceof EnumerablePropertySource)) this.checkConfigurationParameters(true);
continue; } catch (Exception e) {
for (final String name : ((EnumerablePropertySource<?>) propertySource).getPropertyNames()) log.error("Exception checking configuration",e);
props.computeIfAbsent(name, propertySource::getProperty); error(e.getMessage());
}
try {
kmsUrisList = this.kmsUrisStringToList(this.kmsUris);
this.checkFinalWebHookConfiguration();
} catch (Exception e) {
error(e.getMessage());
} }
return props;
} }
private void error(String msg) {
log.error(msg);
this.confErrors.add(msg);
}
private String getPropertyName(String propertyName) {
if (SHOW_PROPERTIES_AS_ENV_VARS) {
return propertyName.replace('.', '_').toUpperCase();
} else {
return propertyName;
}
}
// protected Map<String, Object> getFinalPropertiesInUse() {
// final SortedMap<String, Object> 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;
// }
private String listToQuotedStringifiedArray(List<String> list) { private String listToQuotedStringifiedArray(List<String> list) {
return "[" + list.stream().map(s -> "\"" + s + "\"").collect(Collectors.joining(", ")) + "]"; return "[" + list.stream().map(s -> "\"" + s + "\"").collect(Collectors.joining(", ")) + "]";
} }