diff --git a/openvidu-java-client/pom.xml b/openvidu-java-client/pom.xml index 271679d4..3d6d759b 100644 --- a/openvidu-java-client/pom.xml +++ b/openvidu-java-client/pom.xml @@ -63,11 +63,6 @@ - - org.apache.httpcomponents - httpclient - 4.5.13 - com.google.code.gson gson @@ -106,6 +101,7 @@ ${version.javadoc.plugin} public + io.openvidu.java.client.utils @@ -142,6 +138,7 @@ public ${java.home}/bin/javadoc + io.openvidu.java.client.utils diff --git a/openvidu-java-client/src/main/java/io/openvidu/java/client/IceServerProperties.java b/openvidu-java-client/src/main/java/io/openvidu/java/client/IceServerProperties.java index c044a336..3235e7a4 100644 --- a/openvidu-java-client/src/main/java/io/openvidu/java/client/IceServerProperties.java +++ b/openvidu-java-client/src/main/java/io/openvidu/java/client/IceServerProperties.java @@ -17,19 +17,23 @@ package io.openvidu.java.client; -import com.google.gson.JsonObject; -import org.apache.commons.codec.binary.Base64; -import org.apache.commons.validator.routines.DomainValidator; -import org.apache.commons.validator.routines.InetAddressValidator; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; import java.net.Inet6Address; import java.net.UnknownHostException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; -import java.util.*; +import java.util.Arrays; +import java.util.Base64; +import java.util.HashSet; +import java.util.Set; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; + +import org.apache.commons.validator.routines.DomainValidator; +import org.apache.commons.validator.routines.InetAddressValidator; + +import com.google.gson.JsonObject; /** * See @@ -37,331 +41,352 @@ import java.util.*; */ public class IceServerProperties { - private String url; - private String username; - private String credential; + private String url; + private String username; + private String credential; - /** - * Returns the defined ICE Server url for this {@link IceServerProperties} object. - */ - public String getUrl() { - return url; - } + /** + * Returns the defined ICE Server url for this {@link IceServerProperties} + * object. + */ + public String getUrl() { + return url; + } - /** - * Returns the Username to be used for TURN connections at the defined {@link IceServerProperties#getUrl()} - * and {@link IceServerProperties#getCredential()} for this {@link IceServerProperties} object. - */ - public String getUsername() { - return username; - } + /** + * Returns the Username to be used for TURN connections at the defined + * {@link IceServerProperties#getUrl()} and + * {@link IceServerProperties#getCredential()} for this + * {@link IceServerProperties} object. + */ + public String getUsername() { + return username; + } - /** - * Returns the credential to be used for TURN connections at the defined {@link IceServerProperties#getUrl()} - * and {@link IceServerProperties#getUsername()} for this {@link IceServerProperties} object. - */ - public String getCredential() { - return credential; - } + /** + * Returns the credential to be used for TURN connections at the defined + * {@link IceServerProperties#getUrl()} and + * {@link IceServerProperties#getUsername()} for this + * {@link IceServerProperties} object. + */ + public String getCredential() { + return credential; + } - private IceServerProperties(String url, String username, String credential) { - this.url = url; - this.username = username; - this.credential = credential; - } + private IceServerProperties(String url, String username, String credential) { + this.url = url; + this.username = username; + this.credential = credential; + } - /** - * @hidden - */ - public JsonObject toJson() { - JsonObject json = new JsonObject(); - json.addProperty("url", getUrl()); - if (getUsername() != null && !getUsername().isEmpty()) { - json.addProperty("username", getUsername()); - } - if (getCredential() != null && !getCredential().isEmpty()) { - json.addProperty("credential", getCredential()); - } - return json; - } + /** + * @hidden + */ + public JsonObject toJson() { + JsonObject json = new JsonObject(); + json.addProperty("url", getUrl()); + if (getUsername() != null && !getUsername().isEmpty()) { + json.addProperty("username", getUsername()); + } + if (getCredential() != null && !getCredential().isEmpty()) { + json.addProperty("credential", getCredential()); + } + return json; + } - /** - * Builder for {@link IceServerProperties} - */ - public static class Builder { + /** + * Builder for {@link IceServerProperties} + */ + public static class Builder { - private String url; - private String username; - private String credential; - private String staticAuthSecret; - private boolean ignoreEmptyUrl = false; + private String url; + private String username; + private String credential; + private String staticAuthSecret; + private boolean ignoreEmptyUrl = false; - /** - * Set the url for the ICE Server you want to use. - * It should follow a valid format: - * - */ - public IceServerProperties.Builder url(String url) { - this.url = url; - return this; - } + /** + * Set the url for the ICE Server you want to use. It should follow a valid + * format: + * + */ + public IceServerProperties.Builder url(String url) { + this.url = url; + return this; + } - /** - * Set a username for the ICE Server you want to use. - * This parameter should be defined only for TURN, not for STUN ICE Servers. - */ - public IceServerProperties.Builder username(String userName) { - this.username = userName; - return this; - } + /** + * Set a username for the ICE Server you want to use. This parameter should be + * defined only for TURN, not for STUN ICE Servers. + */ + public IceServerProperties.Builder username(String userName) { + this.username = userName; + return this; + } - /** - * Set a credential for the ICE Server you want to use. - * This parameter should be defined only for TURN, not for STUN ICE Servers. - */ - public IceServerProperties.Builder credential(String credential) { - this.credential = credential; - return this; - } + /** + * Set a credential for the ICE Server you want to use. This parameter should be + * defined only for TURN, not for STUN ICE Servers. + */ + public IceServerProperties.Builder credential(String credential) { + this.credential = credential; + return this; + } - /** - * Secret for TURN authentication based on: - * - * This will generate credentials valid for 24 hours which is the recommended value. You need to setup in your TURN service this same secret value - * which uses HMAC SHA1 as encryption algorithm. A TURN implementation which by default uses this is COTURN with static-auth-secret parameter. - */ - public IceServerProperties.Builder staticAuthSecret(String staticAuthSecret) { - this.staticAuthSecret = staticAuthSecret; - return this; - } + /** + * Secret for TURN authentication based on: + * + * This will generate credentials valid for 24 hours which is the recommended + * value. You need to setup in your TURN service this same secret value which + * uses HMAC SHA1 as encryption algorithm. A TURN implementation which by + * default uses this is COTURN with static-auth-secret parameter. + */ + public IceServerProperties.Builder staticAuthSecret(String staticAuthSecret) { + this.staticAuthSecret = staticAuthSecret; + return this; + } - public IceServerProperties.Builder ignoreEmptyUrl(boolean ignore) { - this.ignoreEmptyUrl = true; - return this; - } + public IceServerProperties.Builder ignoreEmptyUrl(boolean ignore) { + this.ignoreEmptyUrl = true; + return this; + } - public IceServerProperties.Builder clone() { - return new Builder().url(this.url) - .username(this.username) - .credential(this.credential) - .staticAuthSecret(this.staticAuthSecret); - } + public IceServerProperties.Builder clone() { + return new Builder().url(this.url).username(this.username).credential(this.credential) + .staticAuthSecret(this.staticAuthSecret); + } - /** - * Builder for {@link io.openvidu.java.client.RecordingProperties} - * @throws IllegalArgumentException if the defined properties does not follows - * common STUN/TURN RFCs: - * - */ - public IceServerProperties build() throws IllegalArgumentException { - if (this.ignoreEmptyUrl) { - if (this.staticAuthSecret != null && this.username == null && this.credential == null) { - try { - this.generateTURNCredentials(); - return new IceServerProperties(this.url, this.username, this.credential); - } catch (NoSuchAlgorithmException | InvalidKeyException e) { - throw new IllegalArgumentException("Error while generating credentials: " + e.getMessage()); - } - } else { - throw new IllegalArgumentException("ignoreEmptyUrl=true can only be used with staticAuthSecret defined"); - } - } - if (this.url == null) { - throw new IllegalArgumentException("External turn url cannot be null"); - } - this.checkValidStunTurn(this.url); - if (this.url.startsWith("turn")) { - if (this.staticAuthSecret != null) { - if (this.username != null || this.credential != null) { - throw new IllegalArgumentException("You can't define username or credential if staticAuthSecret is defined"); - } - try { - this.generateTURNCredentials(); - } catch (NoSuchAlgorithmException | InvalidKeyException e) { - throw new IllegalArgumentException("Error while generating credentials: " + e.getMessage()); - } - } - if ((this.username == null || this.credential == null)) { - throw new IllegalArgumentException("Credentials must be defined while using turn"); - } - } else if (this.url.startsWith("stun")) { - if (this.username != null || this.credential != null) { - // Credentials can not be defined using stun - throw new IllegalArgumentException("Credentials can not be defined while using stun."); - } - } - return new IceServerProperties(this.url, this.username, this.credential); - } + /** + * Builder for {@link io.openvidu.java.client.RecordingProperties} + * + * @throws IllegalArgumentException if the defined properties does not follows + * common STUN/TURN RFCs: + * + */ + public IceServerProperties build() throws IllegalArgumentException { + if (this.ignoreEmptyUrl) { + if (this.staticAuthSecret != null && this.username == null && this.credential == null) { + try { + this.generateTURNCredentials(); + return new IceServerProperties(this.url, this.username, this.credential); + } catch (NoSuchAlgorithmException | InvalidKeyException e) { + throw new IllegalArgumentException("Error while generating credentials: " + e.getMessage()); + } + } else { + throw new IllegalArgumentException( + "ignoreEmptyUrl=true can only be used with staticAuthSecret defined"); + } + } + if (this.url == null) { + throw new IllegalArgumentException("External turn url cannot be null"); + } + this.checkValidStunTurn(this.url); + if (this.url.startsWith("turn")) { + if (this.staticAuthSecret != null) { + if (this.username != null || this.credential != null) { + throw new IllegalArgumentException( + "You can't define username or credential if staticAuthSecret is defined"); + } + try { + this.generateTURNCredentials(); + } catch (NoSuchAlgorithmException | InvalidKeyException e) { + throw new IllegalArgumentException("Error while generating credentials: " + e.getMessage()); + } + } + if ((this.username == null || this.credential == null)) { + throw new IllegalArgumentException("Credentials must be defined while using turn"); + } + } else if (this.url.startsWith("stun")) { + if (this.username != null || this.credential != null) { + // Credentials can not be defined using stun + throw new IllegalArgumentException("Credentials can not be defined while using stun."); + } + } + return new IceServerProperties(this.url, this.username, this.credential); + } - private void checkValidStunTurn(String uri) throws IllegalArgumentException { - final String TCP_TRANSPORT_SUFFIX = "?transport=tcp"; - final String UDP_TRANSPORT_SUFFIX = "?transport=udp"; + private void checkValidStunTurn(String uri) throws IllegalArgumentException { + final String TCP_TRANSPORT_SUFFIX = "?transport=tcp"; + final String UDP_TRANSPORT_SUFFIX = "?transport=udp"; - // Protocols which accepts transport=tcp and transport=udp - final Set TURN_PROTOCOLS = new HashSet<>(Arrays.asList( - "turn", - "turns" - )); - final Set STUN_PROTOCOLS = new HashSet<>(Arrays.asList( - "stun", - "stuns" - )); + // Protocols which accepts transport=tcp and transport=udp + final Set TURN_PROTOCOLS = new HashSet<>(Arrays.asList("turn", "turns")); + final Set STUN_PROTOCOLS = new HashSet<>(Arrays.asList("stun", "stuns")); - // Fails if no colons - int firstColonPos = uri.indexOf(':'); - if (firstColonPos == -1) { - throw new IllegalArgumentException("Not a valid TURN/STUN uri provided. " + - "No colons found in: '" + uri + "'"); - } + // Fails if no colons + int firstColonPos = uri.indexOf(':'); + if (firstColonPos == -1) { + throw new IllegalArgumentException( + "Not a valid TURN/STUN uri provided. " + "No colons found in: '" + uri + "'"); + } - // Get protocol and check - String protocol = uri.substring(0, firstColonPos); - if (!TURN_PROTOCOLS.contains(protocol) && !STUN_PROTOCOLS.contains(protocol)) { - throw new IllegalArgumentException("The protocol '" + protocol + "' is invalid. Only valid values are: " - + TURN_PROTOCOLS + " " + STUN_PROTOCOLS); - } + // Get protocol and check + String protocol = uri.substring(0, firstColonPos); + if (!TURN_PROTOCOLS.contains(protocol) && !STUN_PROTOCOLS.contains(protocol)) { + throw new IllegalArgumentException("The protocol '" + protocol + "' is invalid. Only valid values are: " + + TURN_PROTOCOLS + " " + STUN_PROTOCOLS); + } - // Check if query param with transport exist - int qmarkPos = uri.indexOf('?'); - String hostAndPort = uri.substring(firstColonPos + 1); - if (qmarkPos != -1) { - if (TURN_PROTOCOLS.contains(protocol)) { - // Only Turn uses transport arg - String rawTransportType = uri.substring(qmarkPos); - hostAndPort = uri.substring(firstColonPos + 1, qmarkPos); - if (!TCP_TRANSPORT_SUFFIX.equals(rawTransportType) && !UDP_TRANSPORT_SUFFIX.equals(rawTransportType)) { - // If other argument rather than transport is specified, it is a wrong query for a STUN/TURN uri - throw new IllegalArgumentException("Wrong value specified in STUN/TURN uri: '" - + uri + "'. " + "Unique valid arguments after '?' are '" - + TCP_TRANSPORT_SUFFIX + "' or '" + UDP_TRANSPORT_SUFFIX); - } - } else { - throw new IllegalArgumentException("STUN uri can't have any '?' query param"); - } - } + // Check if query param with transport exist + int qmarkPos = uri.indexOf('?'); + String hostAndPort = uri.substring(firstColonPos + 1); + if (qmarkPos != -1) { + if (TURN_PROTOCOLS.contains(protocol)) { + // Only Turn uses transport arg + String rawTransportType = uri.substring(qmarkPos); + hostAndPort = uri.substring(firstColonPos + 1, qmarkPos); + if (!TCP_TRANSPORT_SUFFIX.equals(rawTransportType) + && !UDP_TRANSPORT_SUFFIX.equals(rawTransportType)) { + // If other argument rather than transport is specified, it is a wrong query for + // a STUN/TURN uri + throw new IllegalArgumentException("Wrong value specified in STUN/TURN uri: '" + uri + "'. " + + "Unique valid arguments after '?' are '" + TCP_TRANSPORT_SUFFIX + "' or '" + + UDP_TRANSPORT_SUFFIX); + } + } else { + throw new IllegalArgumentException("STUN uri can't have any '?' query param"); + } + } - // Check if port is defined - int portColon = hostAndPort.indexOf(':'); - // IPv6 are defined between brackets - int startIpv6Index = hostAndPort.indexOf('['); - int endIpv6Index = hostAndPort.indexOf(']'); - if (startIpv6Index == -1 ^ endIpv6Index == -1) { - throw new IllegalArgumentException("Not closed bracket '[' or ']' in uri: " + uri); - } + // Check if port is defined + int portColon = hostAndPort.indexOf(':'); + // IPv6 are defined between brackets + int startIpv6Index = hostAndPort.indexOf('['); + int endIpv6Index = hostAndPort.indexOf(']'); + if (startIpv6Index == -1 ^ endIpv6Index == -1) { + throw new IllegalArgumentException("Not closed bracket '[' or ']' in uri: " + uri); + } - if (portColon != -1) { - if (startIpv6Index == -1 && endIpv6Index == -1) { - // If Ipv4 and port defined - String[] splittedHostAndPort = hostAndPort.split(":"); - if (splittedHostAndPort.length != 2) { - throw new IllegalArgumentException("Host or port are not correctly " + - "defined in STUN/TURN uri: '" + uri + "'"); - } - String host = splittedHostAndPort[0]; - String port = splittedHostAndPort[1]; + if (portColon != -1) { + if (startIpv6Index == -1 && endIpv6Index == -1) { + // If Ipv4 and port defined + String[] splittedHostAndPort = hostAndPort.split(":"); + if (splittedHostAndPort.length != 2) { + throw new IllegalArgumentException( + "Host or port are not correctly " + "defined in STUN/TURN uri: '" + uri + "'"); + } + String host = splittedHostAndPort[0]; + String port = splittedHostAndPort[1]; - // Check if host is defined. Valid Host (Domain or IP) will be done at server side - checkHostAndPort(uri, host, port); - } else { - // If portColon is found and Ipv6 - String ipv6 = hostAndPort.substring(startIpv6Index + 1, endIpv6Index); - String auxPort = hostAndPort.substring(endIpv6Index + 1); - if (auxPort.startsWith(":")) { - if (auxPort.length() == 1) { - throw new IllegalArgumentException("Host or port are not correctly defined in STUN/TURN uri: " + uri); - } - // If port is defined - // Get port without colon and check host and port - String host = ipv6; - String port = auxPort.substring(1); - checkHostAndPort(uri, host, port); - } else if (auxPort.length() > 0) { - // If auxPort = 0, no port is defined - throw new IllegalArgumentException("Port is not specified correctly after IPv6 in uri: '" + uri + "'"); - } - } - } else { - // If portColon not found, only host is defined - String host = hostAndPort; - checkHost(uri, host); - } - } + // Check if host is defined. Valid Host (Domain or IP) will be done at server + // side + checkHostAndPort(uri, host, port); + } else { + // If portColon is found and Ipv6 + String ipv6 = hostAndPort.substring(startIpv6Index + 1, endIpv6Index); + String auxPort = hostAndPort.substring(endIpv6Index + 1); + if (auxPort.startsWith(":")) { + if (auxPort.length() == 1) { + throw new IllegalArgumentException( + "Host or port are not correctly defined in STUN/TURN uri: " + uri); + } + // If port is defined + // Get port without colon and check host and port + String host = ipv6; + String port = auxPort.substring(1); + checkHostAndPort(uri, host, port); + } else if (auxPort.length() > 0) { + // If auxPort = 0, no port is defined + throw new IllegalArgumentException( + "Port is not specified correctly after IPv6 in uri: '" + uri + "'"); + } + } + } else { + // If portColon not found, only host is defined + String host = hostAndPort; + checkHost(uri, host); + } + } - private void checkHost(String uri, String host) { - if (host == null || host.isEmpty()) { - throw new IllegalArgumentException("Host defined in '" + uri + "' is empty or null"); - } - if (DomainValidator.getInstance().isValid(host)) { - return; - } - InetAddressValidator ipValidator = InetAddressValidator.getInstance(); - if (ipValidator.isValid(host)) { - return; - } - try { - Inet6Address.getByName(host).getHostAddress(); - return; - } catch (UnknownHostException e) { - throw new IllegalArgumentException("Is not a valid Internet Address (IP or Domain Name): '" + host + "'"); - } - } + private void checkHost(String uri, String host) { + if (host == null || host.isEmpty()) { + throw new IllegalArgumentException("Host defined in '" + uri + "' is empty or null"); + } + if (DomainValidator.getInstance().isValid(host)) { + return; + } + InetAddressValidator ipValidator = InetAddressValidator.getInstance(); + if (ipValidator.isValid(host)) { + return; + } + try { + Inet6Address.getByName(host).getHostAddress(); + return; + } catch (UnknownHostException e) { + throw new IllegalArgumentException( + "Is not a valid Internet Address (IP or Domain Name): '" + host + "'"); + } + } - private void checkPort(String uri, String port) { - if (port == null || port.isEmpty()) { - throw new IllegalArgumentException("Port defined in '" + uri + "' is empty or null"); - } + private void checkPort(String uri, String port) { + if (port == null || port.isEmpty()) { + throw new IllegalArgumentException("Port defined in '" + uri + "' is empty or null"); + } - try { - int parsedPort = Integer.parseInt(port); - if (parsedPort <= 0 || parsedPort > 65535) { - throw new IllegalArgumentException("The port defined in '" + uri + "' is not a valid port number (0-65535)"); - } - } catch (NumberFormatException e) { - throw new IllegalArgumentException("The port defined in '" + uri + "' is not a number (0-65535)"); - } - } + try { + int parsedPort = Integer.parseInt(port); + if (parsedPort <= 0 || parsedPort > 65535) { + throw new IllegalArgumentException( + "The port defined in '" + uri + "' is not a valid port number (0-65535)"); + } + } catch (NumberFormatException e) { + throw new IllegalArgumentException("The port defined in '" + uri + "' is not a number (0-65535)"); + } + } - private void checkHostAndPort(String uri, String host, String port) { - this.checkHost(uri, host); - this.checkPort(uri, port); - } + private void checkHostAndPort(String uri, String host, String port) { + this.checkHost(uri, host); + this.checkPort(uri, port); + } + private void generateTURNCredentials() throws NoSuchAlgorithmException, InvalidKeyException { + // 1. Generate random username + char[] ALPHANUMERIC = ("abcdefghijklmnopqrstuvwxyzABCDEFGHIJK" + "LMNOPQRSTUVWXYZ0123456789").toCharArray(); + int MAX_LENGTH = 8; + StringBuilder randomUsername = new StringBuilder(); + for (int i = 0; i < MAX_LENGTH; i++) { + int index = new SecureRandom().nextInt(ALPHANUMERIC.length); + randomUsername.append(ALPHANUMERIC[index]); + } + // 2. Get unix timestamp adding 24 hours to define max credential valid time + String unixTimestamp = Long.toString((System.currentTimeMillis() / 1000) + 24 * 3600); - private void generateTURNCredentials() throws NoSuchAlgorithmException, InvalidKeyException { - // 1. Generate random username - char[] ALPHANUMERIC =("abcdefghijklmnopqrstuvwxyzABCDEFGHIJK" + - "LMNOPQRSTUVWXYZ0123456789").toCharArray(); - int MAX_LENGTH = 8; - StringBuilder randomUsername = new StringBuilder(); - for(int i =0; i < MAX_LENGTH; i++) { - int index = new SecureRandom().nextInt(ALPHANUMERIC.length); - randomUsername.append(ALPHANUMERIC[index]); - } - // 2. Get unix timestamp adding 24 hours to define max credential valid time - String unixTimestamp = Long.toString((System.currentTimeMillis() / 1000) + 24*3600); + // 3. Generate TURN username + String username = unixTimestamp + ":" + randomUsername; - // 3. Generate TURN username - String username = unixTimestamp + ":" + randomUsername; + // 4. Generate HMAC SHA-1 password + SecretKeySpec signingKey = new SecretKeySpec(staticAuthSecret.getBytes(), "HmacSHA1"); + Mac mac = Mac.getInstance("HmacSHA1"); + mac.init(signingKey); + String credential = new String(Base64.getEncoder().encode(mac.doFinal(username.getBytes()))); - // 4. Generate HMAC SHA-1 password - SecretKeySpec signingKey = new SecretKeySpec(staticAuthSecret.getBytes(), "HmacSHA1"); - Mac mac = Mac.getInstance("HmacSHA1"); - mac.init(signingKey); - String credential = new String(Base64.encodeBase64(mac.doFinal(username.getBytes()))); - - // Set credentials in builder - this.username = username; - this.credential = credential; - } - } + // Set credentials in builder + this.username = username; + this.credential = credential; + } + } } diff --git a/openvidu-java-client/src/main/java/io/openvidu/java/client/OpenVidu.java b/openvidu-java-client/src/main/java/io/openvidu/java/client/OpenVidu.java index 00e00281..a2d31b50 100644 --- a/openvidu-java-client/src/main/java/io/openvidu/java/client/OpenVidu.java +++ b/openvidu-java-client/src/main/java/io/openvidu/java/client/OpenVidu.java @@ -18,55 +18,49 @@ package io.openvidu.java.client; import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.MalformedURLException; +import java.net.Socket; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.http.HttpClient; +import java.net.http.HttpClient.Builder; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; import java.security.KeyManagementException; -import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; -import java.security.cert.CertificateException; import java.security.cert.X509Certificate; +import java.time.Duration; import java.util.ArrayList; +import java.util.Base64; +import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509ExtendedTrustManager; -import org.apache.http.HttpHeaders; -import org.apache.http.HttpResponse; -import org.apache.http.ParseException; -import org.apache.http.auth.AuthScope; -import org.apache.http.auth.UsernamePasswordCredentials; -import org.apache.http.client.CredentialsProvider; -import org.apache.http.client.HttpClient; -import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.methods.HttpDelete; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.conn.ssl.NoopHostnameVerifier; -import org.apache.http.entity.StringEntity; -import org.apache.http.impl.client.BasicCredentialsProvider; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.ssl.SSLContextBuilder; -import org.apache.http.ssl.TrustStrategy; -import org.apache.http.util.EntityUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonObject; -import com.google.gson.JsonSyntaxException; public class OpenVidu { private static final Logger log = LoggerFactory.getLogger(OpenVidu.class); - private String secret; protected String hostname; protected HttpClient httpClient; protected Map activeSessions = new ConcurrentHashMap<>(); + protected long requestTimeout = 30000; + protected Map headers = new HashMap<>(); protected final static String API_PATH = "openvidu/api"; protected final static String API_SESSIONS = API_PATH + "/sessions"; @@ -75,49 +69,149 @@ public class OpenVidu { protected final static String API_RECORDINGS_START = API_RECORDINGS + "/start"; protected final static String API_RECORDINGS_STOP = API_RECORDINGS + "/stop"; + private String defaultBasicAuth; + /** - * @param hostname URL where your instance of OpenVidu Server is up an running. - * It must be the full URL (e.g. - * https://12.34.56.78:1234/) + * @param hostname URL where your OpenVidu deployment is up an running. It must + * be the full URL (e.g. https://12.34.56.78:1234/) * - * @param secret Secret used on OpenVidu Server initialization + * @param secret Secret configured in your OpenVidu deployment */ public OpenVidu(String hostname, String secret) { - this.hostname = hostname; + testHostname(hostname); + setDefaultBasicAuth(secret); + this.hostname = hostname; if (!this.hostname.endsWith("/")) { this.hostname += "/"; } - this.secret = secret; - - TrustStrategy trustStrategy = new TrustStrategy() { - @Override - public boolean isTrusted(X509Certificate[] chain, String authType) throws CertificateException { - return true; - } - }; - - CredentialsProvider provider = new BasicCredentialsProvider(); - UsernamePasswordCredentials credentials = new UsernamePasswordCredentials("OPENVIDUAPP", this.secret); - provider.setCredentials(AuthScope.ANY, credentials); - SSLContext sslContext; - try { - sslContext = new SSLContextBuilder().loadTrustMaterial(null, trustStrategy).build(); - } catch (KeyManagementException | NoSuchAlgorithmException | KeyStoreException e) { + sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, new TrustManager[] { new X509ExtendedTrustManager() { + public X509Certificate[] getAcceptedIssuers() { + return null; + } + + public void checkClientTrusted(final X509Certificate[] a_certificates, final String a_auth_type) { + } + + public void checkServerTrusted(final X509Certificate[] a_certificates, final String a_auth_type) { + } + + public void checkClientTrusted(final X509Certificate[] a_certificates, final String a_auth_type, + final Socket a_socket) { + } + + public void checkServerTrusted(final X509Certificate[] a_certificates, final String a_auth_type, + final Socket a_socket) { + } + + public void checkClientTrusted(final X509Certificate[] a_certificates, final String a_auth_type, + final SSLEngine a_engine) { + } + + public void checkServerTrusted(final X509Certificate[] a_certificates, final String a_auth_type, + final SSLEngine a_engine) { + } + } }, null); + } catch (KeyManagementException | NoSuchAlgorithmException e) { throw new RuntimeException(e); } - RequestConfig.Builder requestBuilder = RequestConfig.custom(); - requestBuilder = requestBuilder.setConnectTimeout(30000); - requestBuilder = requestBuilder.setConnectionRequestTimeout(30000); + Builder b = HttpClient.newBuilder(); + b.sslContext(sslContext); + b.connectTimeout(Duration.ofSeconds(30)); + this.httpClient = b.build(); + } - this.httpClient = HttpClientBuilder.create().setDefaultRequestConfig(requestBuilder.build()) - .setConnectionTimeToLive(30, TimeUnit.SECONDS).setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE) - .setSSLContext(sslContext).setDefaultCredentialsProvider(provider).build(); + /** + * @param hostname URL where your OpenVidu deployment is up an running. It + * must be the full URL (e.g. + * https://12.34.56.78:1234/) + * @param httpClient Object of class java.net.http.HttpClient. + * This overrides the internal HTTP client in use. This method + * allows you to custom configure the HTTP client to your + * needs. This may be interesting for many reasons, including: + *
    + *
  • Adding proxy configuration
  • + *
  • Customizing the SSLContext
  • + *
  • Modifying the connection timeout
  • + *
  • Adding a cookie handler
  • + *
+ */ + public OpenVidu(String hostname, String secret, HttpClient httpClient) { + + testHostname(hostname); + setDefaultBasicAuth(secret); + + this.hostname = hostname; + if (!this.hostname.endsWith("/")) { + this.hostname += "/"; + } + + Builder b = HttpClient.newBuilder(); + if (httpClient.authenticator().isPresent()) { + log.warn( + "The provided HttpClient contains a custom java.net.Authenticator. OpenVidu deployments require all HTTP requests to have an Authorization header with Basic Auth, with username \"OPENVIDUAPP\" and password your OpenVidu deployment's secret (configuration parameter OPENVIDU_SECRET). The default Authenticator adds this header. Make sure that your custom Authenticator does so as well or that you add it explicitly via OpenVidu#setRequestHeaders, or you will receive 401 responses"); + b.authenticator(httpClient.authenticator().get()); + } + if (httpClient.connectTimeout().isPresent()) { + b.connectTimeout(httpClient.connectTimeout().get()); + } + if (httpClient.cookieHandler().isPresent()) { + b.cookieHandler(httpClient.cookieHandler().get()); + } + if (httpClient.executor().isPresent()) { + b.executor(httpClient.executor().get()); + } + if (httpClient.proxy().isPresent()) { + b.proxy(httpClient.proxy().get()); + } + b.followRedirects(httpClient.followRedirects()); + b.sslContext(httpClient.sslContext()); + b.sslParameters(httpClient.sslParameters()); + b.version(httpClient.version()); + + this.httpClient = b.build(); + } + + /** + * Returns the current request timeout configured for all requests. + */ + public long getRequestTimeout() { + return this.requestTimeout; + } + + /** + * Sets the request timeout for all HTTP requests. This is the same as setting + * the HttpRequest.timeout() + * property for all requests. The default value is 30000 ms. + * + * @param requestTimeout Timeout in milliseconds + */ + public void setRequestTimeout(long requestTimeout) { + this.requestTimeout = requestTimeout; + } + + /** + * Returns the current collection of custom headers configured for all requests. + */ + public Map getRequestHeaders() { + return this.headers; + } + + /** + * Sets custom HTTP headers for all requests. Will override any previous value. + * + * @param headers Header names and values + */ + public void setRequestHeaders(Map headers) { + this.headers = headers; } /** @@ -183,23 +277,20 @@ public class OpenVidu { public Recording startRecording(String sessionId, RecordingProperties properties) throws OpenViduJavaClientException, OpenViduHttpException { - HttpPost request = new HttpPost(this.hostname + API_RECORDINGS_START); - - JsonObject json = properties.toJson(); - json.addProperty("session", sessionId); - - StringEntity params = new StringEntity(json.toString(), "UTF-8"); - - request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json"); - request.setEntity(params); - - HttpResponse response = null; try { - response = this.httpClient.execute(request); + JsonObject json = properties.toJson(); + json.addProperty("session", sessionId); - int statusCode = response.getStatusLine().getStatusCode(); - if ((statusCode == org.apache.http.HttpStatus.SC_OK)) { - Recording r = new Recording(httpResponseToJson(response)); + HttpRequest request = addHeaders( + HttpRequest.newBuilder().POST(HttpRequest.BodyPublishers.ofString(json.toString())) + .uri(new URI(this.hostname + API_RECORDINGS_START)) + .setHeader("Content-Type", "application/json").timeout(Duration.ofMillis(requestTimeout))) + .build(); + + HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + + if ((response.statusCode() == HttpURLConnection.HTTP_OK)) { + Recording r = new Recording(Utils.httpResponseToJson(response)); Session activeSession = this.activeSessions.get(r.getSessionId()); if (activeSession != null) { activeSession.setIsBeingRecorded(true); @@ -210,15 +301,11 @@ public class OpenVidu { } return r; } else { - throw new OpenViduHttpException(statusCode); + throw new OpenViduHttpException(response.statusCode()); } - } catch (IOException e) { + } catch (URISyntaxException | IOException | InterruptedException e) { throw new OpenViduJavaClientException(e.getMessage(), e.getCause()); - } finally { - if (response != null) { - EntityUtils.consumeQuietly(response.getEntity()); - } } } @@ -288,14 +375,16 @@ public class OpenVidu { * API) */ public Recording stopRecording(String recordingId) throws OpenViduJavaClientException, OpenViduHttpException { - HttpPost request = new HttpPost(this.hostname + API_RECORDINGS_STOP + "/" + recordingId); - HttpResponse response = null; - try { - response = this.httpClient.execute(request); - int statusCode = response.getStatusLine().getStatusCode(); - if ((statusCode == org.apache.http.HttpStatus.SC_OK)) { - Recording r = new Recording(httpResponseToJson(response)); + try { + HttpRequest request = addHeaders(HttpRequest.newBuilder().POST(HttpRequest.BodyPublishers.noBody()) + .uri(new URI(this.hostname + API_RECORDINGS_STOP + "/" + recordingId)) + .timeout(Duration.ofMillis(requestTimeout))).build(); + + HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + + if (response.statusCode() == HttpURLConnection.HTTP_OK) { + Recording r = new Recording(Utils.httpResponseToJson(response)); Session activeSession = this.activeSessions.get(r.getSessionId()); if (activeSession != null) { activeSession.setIsBeingRecorded(false); @@ -306,14 +395,10 @@ public class OpenVidu { } return r; } else { - throw new OpenViduHttpException(statusCode); + throw new OpenViduHttpException(response.statusCode()); } - } catch (IOException e) { + } catch (URISyntaxException | IOException | InterruptedException e) { throw new OpenViduJavaClientException(e.getMessage(), e.getCause()); - } finally { - if (response != null) { - EntityUtils.consumeQuietly(response.getEntity()); - } } } @@ -331,24 +416,22 @@ public class OpenVidu { * API) */ public Recording getRecording(String recordingId) throws OpenViduJavaClientException, OpenViduHttpException { - HttpGet request = new HttpGet(this.hostname + API_RECORDINGS + "/" + recordingId); - HttpResponse response = null; + try { - response = this.httpClient.execute(request); + HttpRequest request = addHeaders( + HttpRequest.newBuilder().GET().uri(new URI(this.hostname + API_RECORDINGS + "/" + recordingId)) + .timeout(Duration.ofMillis(requestTimeout))) + .build(); - int statusCode = response.getStatusLine().getStatusCode(); - if ((statusCode == org.apache.http.HttpStatus.SC_OK)) { - return new Recording(httpResponseToJson(response)); + HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + + if (response.statusCode() == HttpURLConnection.HTTP_OK) { + return new Recording(Utils.httpResponseToJson(response)); } else { - throw new OpenViduHttpException(statusCode); + throw new OpenViduHttpException(response.statusCode()); } - - } catch (IOException e) { + } catch (URISyntaxException | IOException | InterruptedException e) { throw new OpenViduJavaClientException(e.getMessage(), e.getCause()); - } finally { - if (response != null) { - EntityUtils.consumeQuietly(response.getEntity()); - } } } @@ -361,30 +444,26 @@ public class OpenVidu { * @throws OpenViduHttpException */ public List listRecordings() throws OpenViduJavaClientException, OpenViduHttpException { - HttpGet request = new HttpGet(this.hostname + API_RECORDINGS); - HttpResponse response = null; - try { - response = this.httpClient.execute(request); - int statusCode = response.getStatusLine().getStatusCode(); - if ((statusCode == org.apache.http.HttpStatus.SC_OK)) { + try { + HttpRequest request = addHeaders(HttpRequest.newBuilder().GET().uri(new URI(this.hostname + API_RECORDINGS)) + .timeout(Duration.ofMillis(requestTimeout))).build(); + + HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + + if (response.statusCode() == HttpURLConnection.HTTP_OK) { List recordings = new ArrayList<>(); - JsonObject json = httpResponseToJson(response); + JsonObject json = Utils.httpResponseToJson(response); JsonArray array = json.get("items").getAsJsonArray(); array.forEach(item -> { recordings.add(new Recording(item.getAsJsonObject())); }); return recordings; } else { - throw new OpenViduHttpException(statusCode); + throw new OpenViduHttpException(response.statusCode()); } - - } catch (IOException e) { + } catch (URISyntaxException | IOException | InterruptedException e) { throw new OpenViduJavaClientException(e.getMessage(), e.getCause()); - } finally { - if (response != null) { - EntityUtils.consumeQuietly(response.getEntity()); - } } } @@ -405,22 +484,20 @@ public class OpenVidu { * API) */ public void deleteRecording(String recordingId) throws OpenViduJavaClientException, OpenViduHttpException { - HttpDelete request = new HttpDelete(this.hostname + API_RECORDINGS + "/" + recordingId); - HttpResponse response = null; + try { - response = this.httpClient.execute(request); + HttpRequest request = addHeaders( + HttpRequest.newBuilder().DELETE().uri(new URI(this.hostname + API_RECORDINGS + "/" + recordingId)) + .timeout(Duration.ofMillis(requestTimeout))) + .build(); - int statusCode = response.getStatusLine().getStatusCode(); - if (!(statusCode == org.apache.http.HttpStatus.SC_NO_CONTENT)) { - throw new OpenViduHttpException(statusCode); + HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); + + if (response.statusCode() != HttpURLConnection.HTTP_NO_CONTENT) { + throw new OpenViduHttpException(response.statusCode()); } - - } catch (IOException e) { + } catch (URISyntaxException | IOException | InterruptedException e) { throw new OpenViduJavaClientException(e.getMessage(), e.getCause()); - } finally { - if (response != null) { - EntityUtils.consumeQuietly(response.getEntity()); - } } } @@ -488,16 +565,17 @@ public class OpenVidu { * @throws OpenViduJavaClientException */ public boolean fetch() throws OpenViduJavaClientException, OpenViduHttpException { - HttpGet request = new HttpGet(this.hostname + API_SESSIONS + "?pendingConnections=true"); - HttpResponse response = null; try { - response = this.httpClient.execute(request); + HttpRequest request = addHeaders(HttpRequest.newBuilder().GET() + .uri(new URI(this.hostname + API_SESSIONS + "?pendingConnections=true")) + .timeout(Duration.ofMillis(requestTimeout))).build(); - int statusCode = response.getStatusLine().getStatusCode(); - if ((statusCode == org.apache.http.HttpStatus.SC_OK)) { + HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString()); - JsonObject jsonSessions = httpResponseToJson(response); + if (response.statusCode() == HttpURLConnection.HTTP_OK) { + + JsonObject jsonSessions = Utils.httpResponseToJson(response); JsonArray jsonArraySessions = jsonSessions.get("content").getAsJsonArray(); // Boolean to store if any Session has changed @@ -546,26 +624,35 @@ public class OpenVidu { return hasChanged[0]; } else { - throw new OpenViduHttpException(statusCode); + throw new OpenViduHttpException(response.statusCode()); } - } catch (IOException e) { + } catch (URISyntaxException | IOException | InterruptedException e) { throw new OpenViduJavaClientException(e.getMessage(), e.getCause()); - } finally { - if (response != null) { - EntityUtils.consumeQuietly(response.getEntity()); - } } } - private JsonObject httpResponseToJson(HttpResponse response) throws OpenViduJavaClientException { + protected HttpRequest.Builder addHeaders(HttpRequest.Builder builder) { + // HTTP header names are case insensitive + Map headersLowerCase = new HashMap<>(); + this.headers.forEach((k, v) -> headersLowerCase.put(k.toLowerCase(), v)); + if (!headersLowerCase.containsKey("authorization")) { + // There is no custom Authorization header. Add default Basic Auth for OpenVidu + headersLowerCase.put("authorization", this.defaultBasicAuth); + } + headersLowerCase.forEach((k, v) -> builder.setHeader(k, v)); + return builder; + } + + private void testHostname(String hostnameStr) { try { - JsonObject json = new Gson().fromJson(EntityUtils.toString(response.getEntity(), "UTF-8"), - JsonObject.class); - return json; - } catch (JsonSyntaxException | ParseException | IOException e) { - throw new OpenViduJavaClientException(e.getMessage(), e.getCause()); + new URL(hostnameStr); + } catch (MalformedURLException e) { + throw new RuntimeException("The hostname \"" + hostnameStr + "\" is not a valid URL: " + e.getMessage()); } } + private void setDefaultBasicAuth(String secret) { + this.defaultBasicAuth = "Basic " + Base64.getEncoder().encodeToString(("OPENVIDUAPP:" + secret).getBytes()); + } } diff --git a/openvidu-java-client/src/main/java/io/openvidu/java/client/Recording.java b/openvidu-java-client/src/main/java/io/openvidu/java/client/Recording.java index d709b442..c284c832 100644 --- a/openvidu-java-client/src/main/java/io/openvidu/java/client/Recording.java +++ b/openvidu-java-client/src/main/java/io/openvidu/java/client/Recording.java @@ -238,7 +238,7 @@ public class Recording { * URL of the recording. You can access the file from there. It is * null until recording reaches "ready" or "failed" status. If * - * OpenVidu Server configuration property + * OpenVidu configuration property * OPENVIDU_RECORDING_PUBLIC_ACCESS is false, this path will be * secured with OpenVidu credentials */ diff --git a/openvidu-java-client/src/main/java/io/openvidu/java/client/RecordingProperties.java b/openvidu-java-client/src/main/java/io/openvidu/java/client/RecordingProperties.java index 5b46b728..5028b54a 100644 --- a/openvidu-java-client/src/main/java/io/openvidu/java/client/RecordingProperties.java +++ b/openvidu-java-client/src/main/java/io/openvidu/java/client/RecordingProperties.java @@ -22,7 +22,6 @@ import java.util.Map; import com.google.gson.JsonObject; import io.openvidu.java.client.Recording.OutputMode; -import io.openvidu.java.client.utils.FormatChecker; /** * See @@ -559,7 +558,7 @@ public class RecordingProperties { } if (nameParam != null && !nameParam.isEmpty()) { - if (!FormatChecker.isValidRecordingName(nameParam)) { + if (!Utils.isValidRecordingName(nameParam)) { throw new IllegalArgumentException( "Parameter 'name' is wrong. Must be an alphanumeric string [a-zA-Z0-9_-~]+"); } @@ -620,7 +619,7 @@ public class RecordingProperties { } if (resolutionParam != null) { - if (!FormatChecker.isAcceptableRecordingResolution(resolutionParam)) { + if (!Utils.isAcceptableRecordingResolution(resolutionParam)) { throw new IllegalStateException( "Wrong 'resolution' parameter. Acceptable values from 100 to 1999 for both width and height"); } @@ -630,7 +629,7 @@ public class RecordingProperties { } if (frameRateParam != null) { - if (!FormatChecker.isAcceptableRecordingFrameRate(frameRateParam.intValue())) { + if (!Utils.isAcceptableRecordingFrameRate(frameRateParam.intValue())) { throw new IllegalStateException( "Wrong 'frameRate' parameter. Acceptable values are within range [1,120]"); } @@ -640,7 +639,7 @@ public class RecordingProperties { } if (shmSizeParam != null) { - if (!FormatChecker.isAcceptableRecordingShmSize(shmSizeParam)) { + if (!Utils.isAcceptableRecordingShmSize(shmSizeParam)) { throw new IllegalStateException("Wrong 'shmSize' parameter. Must be 134217728 (128 MB) minimum"); } shmSizeFinal = shmSizeParam; diff --git a/openvidu-java-client/src/main/java/io/openvidu/java/client/Session.java b/openvidu-java-client/src/main/java/io/openvidu/java/client/Session.java index 106e7755..8179a0e9 100644 --- a/openvidu-java-client/src/main/java/io/openvidu/java/client/Session.java +++ b/openvidu-java-client/src/main/java/io/openvidu/java/client/Session.java @@ -18,6 +18,12 @@ package io.openvidu.java.client; import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -25,14 +31,6 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; -import org.apache.http.HttpHeaders; -import org.apache.http.HttpResponse; -import org.apache.http.client.methods.HttpDelete; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpPatch; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.entity.StringEntity; -import org.apache.http.util.EntityUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,7 +38,6 @@ import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; -import com.google.gson.JsonSyntaxException; public class Session { @@ -116,35 +113,34 @@ public class Session { */ @Deprecated public String generateToken(TokenOptions tokenOptions) throws OpenViduJavaClientException, OpenViduHttpException { + if (!this.hasSessionId()) { this.getSessionId(); } - HttpPost request = new HttpPost(this.openVidu.hostname + OpenVidu.API_TOKENS); - StringEntity params = new StringEntity(tokenOptions.toJsonObject(sessionId).toString(), "UTF-8"); - - request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json"); - request.setEntity(params); - - HttpResponse response = null; try { - response = this.openVidu.httpClient.execute(request); + JsonObject json = tokenOptions.toJsonObject(sessionId); - int statusCode = response.getStatusLine().getStatusCode(); - if ((statusCode == org.apache.http.HttpStatus.SC_OK)) { - String token = httpResponseToJson(response).get("id").getAsString(); + HttpRequest request = this.openVidu + .addHeaders(HttpRequest.newBuilder().POST(HttpRequest.BodyPublishers.ofString(json.toString())) + .uri(new URI(this.openVidu.hostname + OpenVidu.API_TOKENS)) + .setHeader("Content-Type", "application/json") + .timeout(Duration.ofMillis(this.openVidu.requestTimeout))) + .build(); + + HttpResponse response = this.openVidu.httpClient.send(request, + HttpResponse.BodyHandlers.ofString()); + + if (response.statusCode() == HttpURLConnection.HTTP_OK) { + String token = Utils.httpResponseToJson(response).get("id").getAsString(); log.info("Returning a TOKEN: {}", token); return token; } else { - throw new OpenViduHttpException(statusCode); + throw new OpenViduHttpException(response.statusCode()); } - } catch (IOException e) { + } catch (URISyntaxException | IOException | InterruptedException e) { throw new OpenViduJavaClientException(e.getMessage(), e.getCause()); - } finally { - if (response != null) { - EntityUtils.consumeQuietly(response.getEntity()); - } } } @@ -179,36 +175,33 @@ public class Session { */ public Connection createConnection(ConnectionProperties connectionProperties) throws OpenViduJavaClientException, OpenViduHttpException { + if (!this.hasSessionId()) { this.getSessionId(); } - HttpPost request = new HttpPost( - this.openVidu.hostname + OpenVidu.API_SESSIONS + "/" + this.sessionId + "/connection"); - StringEntity params = new StringEntity(connectionProperties.toJson(sessionId).toString(), "UTF-8"); - - request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json"); - request.setEntity(params); - - HttpResponse response = null; try { - response = this.openVidu.httpClient.execute(request); + JsonObject json = connectionProperties.toJson(sessionId); - int statusCode = response.getStatusLine().getStatusCode(); - if ((statusCode == org.apache.http.HttpStatus.SC_OK)) { - Connection connection = new Connection(httpResponseToJson(response)); + HttpRequest request = this.openVidu.addHeaders(HttpRequest.newBuilder() + .POST(HttpRequest.BodyPublishers.ofString(json.toString())) + .uri(new URI(this.openVidu.hostname + OpenVidu.API_SESSIONS + "/" + this.sessionId + "/connection")) + .setHeader("Content-Type", "application/json") + .timeout(Duration.ofMillis(this.openVidu.requestTimeout))).build(); + + HttpResponse response = this.openVidu.httpClient.send(request, + HttpResponse.BodyHandlers.ofString()); + + if (response.statusCode() == HttpURLConnection.HTTP_OK) { + Connection connection = new Connection(Utils.httpResponseToJson(response)); this.connections.put(connection.getConnectionId(), connection); return connection; } else { - throw new OpenViduHttpException(statusCode); + throw new OpenViduHttpException(response.statusCode()); } - } catch (IOException e) { + } catch (URISyntaxException | IOException | InterruptedException e) { throw new OpenViduJavaClientException(e.getMessage(), e.getCause()); - } finally { - if (response != null) { - EntityUtils.consumeQuietly(response.getEntity()); - } } } @@ -220,27 +213,24 @@ public class Session { * @throws OpenViduHttpException */ public void close() throws OpenViduJavaClientException, OpenViduHttpException { - HttpDelete request = new HttpDelete(this.openVidu.hostname + OpenVidu.API_SESSIONS + "/" + this.sessionId); - request.setHeader(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded"); - HttpResponse response = null; try { - response = this.openVidu.httpClient.execute(request); + HttpRequest request = this.openVidu.addHeaders(HttpRequest.newBuilder().DELETE() + .uri(new URI(this.openVidu.hostname + OpenVidu.API_SESSIONS + "/" + this.sessionId)) + .timeout(Duration.ofMillis(this.openVidu.requestTimeout))).build(); - int statusCode = response.getStatusLine().getStatusCode(); - if ((statusCode == org.apache.http.HttpStatus.SC_NO_CONTENT)) { + HttpResponse response = this.openVidu.httpClient.send(request, + HttpResponse.BodyHandlers.ofString()); + + if (response.statusCode() == HttpURLConnection.HTTP_NO_CONTENT) { this.openVidu.activeSessions.remove(this.sessionId); log.info("Session {} closed", this.sessionId); } else { - throw new OpenViduHttpException(statusCode); + throw new OpenViduHttpException(response.statusCode()); } - } catch (IOException e) { + } catch (URISyntaxException | IOException | InterruptedException e) { throw new OpenViduJavaClientException(e.getMessage(), e.getCause()); - } finally { - if (response != null) { - EntityUtils.consumeQuietly(response.getEntity()); - } } } @@ -266,32 +256,30 @@ public class Session { * @throws OpenViduJavaClientException */ public boolean fetch() throws OpenViduJavaClientException, OpenViduHttpException { - final String beforeJSON = this.toJson(); - HttpGet request = new HttpGet( - this.openVidu.hostname + OpenVidu.API_SESSIONS + "/" + this.sessionId + "?pendingConnections=true"); - request.setHeader(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded"); - HttpResponse response = null; try { - response = this.openVidu.httpClient.execute(request); + final String beforeJSON = this.toJson(); - int statusCode = response.getStatusLine().getStatusCode(); - if ((statusCode == org.apache.http.HttpStatus.SC_OK)) { - this.resetWithJson(httpResponseToJson(response)); + HttpRequest request = this.openVidu.addHeaders(HttpRequest.newBuilder().GET() + .uri(new URI(this.openVidu.hostname + OpenVidu.API_SESSIONS + "/" + this.sessionId + + "?pendingConnections=true")) + .timeout(Duration.ofMillis(this.openVidu.requestTimeout))).build(); + + HttpResponse response = this.openVidu.httpClient.send(request, + HttpResponse.BodyHandlers.ofString()); + + if (response.statusCode() == HttpURLConnection.HTTP_OK) { + this.resetWithJson(Utils.httpResponseToJson(response)); final String afterJSON = this.toJson(); boolean hasChanged = !beforeJSON.equals(afterJSON); log.info("Session info fetched for session '{}'. Any change: {}", this.sessionId, hasChanged); return hasChanged; } else { - throw new OpenViduHttpException(statusCode); + throw new OpenViduHttpException(response.statusCode()); } - } catch (IOException e) { + } catch (URISyntaxException | IOException | InterruptedException e) { throw new OpenViduJavaClientException(e.getMessage(), e.getCause()); - } finally { - if (response != null) { - EntityUtils.consumeQuietly(response.getEntity()); - } } } @@ -340,16 +328,18 @@ public class Session { * @throws OpenViduHttpException */ public void forceDisconnect(String connectionId) throws OpenViduJavaClientException, OpenViduHttpException { - HttpDelete request = new HttpDelete( - this.openVidu.hostname + OpenVidu.API_SESSIONS + "/" + this.sessionId + "/connection/" + connectionId); - request.setHeader(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded"); - HttpResponse response = null; try { - response = this.openVidu.httpClient.execute(request); - int statusCode = response.getStatusLine().getStatusCode(); - if ((statusCode == org.apache.http.HttpStatus.SC_NO_CONTENT)) { + HttpRequest request = this.openVidu.addHeaders(HttpRequest + .newBuilder().DELETE().uri(new URI(this.openVidu.hostname + OpenVidu.API_SESSIONS + "/" + + this.sessionId + "/connection/" + connectionId)) + .timeout(Duration.ofMillis(this.openVidu.requestTimeout))).build(); + + HttpResponse response = this.openVidu.httpClient.send(request, + HttpResponse.BodyHandlers.ofString()); + + if (response.statusCode() == HttpURLConnection.HTTP_NO_CONTENT) { // Remove connection from activeConnections map Connection connectionClosed = this.connections.remove(connectionId); // Remove every Publisher of the closed connection from every subscriber list of @@ -368,15 +358,11 @@ public class Session { } log.info("Connection {} closed", connectionId); } else { - throw new OpenViduHttpException(statusCode); + throw new OpenViduHttpException(response.statusCode()); } - } catch (IOException e) { + } catch (URISyntaxException | IOException | InterruptedException e) { throw new OpenViduJavaClientException(e.getMessage(), e.getCause()); - } finally { - if (response != null) { - EntityUtils.consumeQuietly(response.getEntity()); - } } } @@ -422,16 +408,20 @@ public class Session { * @throws OpenViduHttpException */ public void forceUnpublish(String streamId) throws OpenViduJavaClientException, OpenViduHttpException { - HttpDelete request = new HttpDelete( - this.openVidu.hostname + OpenVidu.API_SESSIONS + "/" + this.sessionId + "/stream/" + streamId); - request.setHeader(HttpHeaders.CONTENT_TYPE, "application/x-www-form-urlencoded"); - HttpResponse response = null; try { - response = this.openVidu.httpClient.execute(request); - int statusCode = response.getStatusLine().getStatusCode(); - if ((statusCode == org.apache.http.HttpStatus.SC_NO_CONTENT)) { + HttpRequest request = this.openVidu + .addHeaders(HttpRequest.newBuilder().DELETE() + .uri(new URI(this.openVidu.hostname + OpenVidu.API_SESSIONS + "/" + this.sessionId + + "/stream/" + streamId)) + .timeout(Duration.ofMillis(this.openVidu.requestTimeout))) + .build(); + + HttpResponse response = this.openVidu.httpClient.send(request, + HttpResponse.BodyHandlers.ofString()); + + if (response.statusCode() == HttpURLConnection.HTTP_NO_CONTENT) { for (Connection connection : this.connections.values()) { // Try to remove the Publisher from the Connection publishers collection if (connection.publishers.remove(streamId) != null) { @@ -442,15 +432,11 @@ public class Session { } log.info("Stream {} unpublished", streamId); } else { - throw new OpenViduHttpException(statusCode); + throw new OpenViduHttpException(response.statusCode()); } - } catch (IOException e) { + } catch (URISyntaxException | IOException | InterruptedException e) { throw new OpenViduJavaClientException(e.getMessage(), e.getCause()); - } finally { - if (response != null) { - EntityUtils.consumeQuietly(response.getEntity()); - } } } @@ -495,26 +481,28 @@ public class Session { public Connection updateConnection(String connectionId, ConnectionProperties connectionProperties) throws OpenViduJavaClientException, OpenViduHttpException { - HttpPatch request = new HttpPatch( - this.openVidu.hostname + OpenVidu.API_SESSIONS + "/" + this.sessionId + "/connection/" + connectionId); - StringEntity params = new StringEntity(connectionProperties.toJson(this.sessionId).toString(), "UTF-8"); - - request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json"); - request.setEntity(params); - - HttpResponse response = null; try { - response = this.openVidu.httpClient.execute(request); - int statusCode = response.getStatusLine().getStatusCode(); - if ((statusCode == org.apache.http.HttpStatus.SC_OK)) { + JsonObject jsonPayload = connectionProperties.toJson(this.sessionId); + + HttpRequest request = this.openVidu.addHeaders(HttpRequest.newBuilder() + .method("PATCH", HttpRequest.BodyPublishers.ofString(jsonPayload.toString())) + .uri(new URI(this.openVidu.hostname + OpenVidu.API_SESSIONS + "/" + this.sessionId + "/connection/" + + connectionId)) + .setHeader("Content-Type", "application/json") + .timeout(Duration.ofMillis(this.openVidu.requestTimeout))).build(); + + HttpResponse response = this.openVidu.httpClient.send(request, + HttpResponse.BodyHandlers.ofString()); + + if (response.statusCode() == HttpURLConnection.HTTP_OK) { log.info("Connection {} updated", connectionId); - } else if ((statusCode == org.apache.http.HttpStatus.SC_NO_CONTENT)) { + } else if (response.statusCode() == HttpURLConnection.HTTP_NO_CONTENT) { log.info("Properties of Connection {} remain the same", connectionId); } else { - throw new OpenViduHttpException(statusCode); + throw new OpenViduHttpException(response.statusCode()); } - JsonObject json = httpResponseToJson(response); + JsonObject json = Utils.httpResponseToJson(response); // Update the actual Connection object with the new options Connection existingConnection = this.connections.get(connectionId); @@ -530,12 +518,8 @@ public class Session { return existingConnection; } - } catch (IOException e) { + } catch (URISyntaxException | IOException | InterruptedException e) { throw new OpenViduJavaClientException(e.getMessage(), e.getCause()); - } finally { - if (response != null) { - EntityUtils.consumeQuietly(response.getEntity()); - } } } @@ -642,23 +626,27 @@ public class Session { } private void getSessionHttp() throws OpenViduJavaClientException, OpenViduHttpException { + if (this.hasSessionId()) { return; } - HttpPost request = new HttpPost(this.openVidu.hostname + OpenVidu.API_SESSIONS); - StringEntity params = new StringEntity(properties.toJson().toString(), "UTF-8"); - - request.setHeader(HttpHeaders.CONTENT_TYPE, "application/json"); - request.setEntity(params); - - HttpResponse response = null; try { - response = this.openVidu.httpClient.execute(request); - int statusCode = response.getStatusLine().getStatusCode(); - if ((statusCode == org.apache.http.HttpStatus.SC_OK)) { - JsonObject responseJson = httpResponseToJson(response); + JsonObject json = properties.toJson(); + + HttpRequest request = this.openVidu + .addHeaders(HttpRequest.newBuilder().POST(HttpRequest.BodyPublishers.ofString(json.toString())) + .uri(new URI(this.openVidu.hostname + OpenVidu.API_SESSIONS)) + .setHeader("Content-Type", "application/json") + .timeout(Duration.ofMillis(this.openVidu.requestTimeout))) + .build(); + + HttpResponse response = this.openVidu.httpClient.send(request, + HttpResponse.BodyHandlers.ofString()); + + if (response.statusCode() == HttpURLConnection.HTTP_OK) { + JsonObject responseJson = Utils.httpResponseToJson(response); this.sessionId = responseJson.get("id").getAsString(); this.createdAt = responseJson.get("createdAt").getAsLong(); @@ -675,31 +663,17 @@ public class Session { this.properties = responseProperties; log.info("Session '{}' created", this.sessionId); - } else if (statusCode == org.apache.http.HttpStatus.SC_CONFLICT) { + } else if (response.statusCode() == HttpURLConnection.HTTP_CONFLICT) { // 'customSessionId' already existed this.sessionId = properties.customSessionId(); this.fetch(); } else { - throw new OpenViduHttpException(statusCode); + throw new OpenViduHttpException(response.statusCode()); } - } catch (IOException e) { - throw new OpenViduJavaClientException(e.getMessage(), e.getCause()); - } finally { - if (response != null) { - EntityUtils.consumeQuietly(response.getEntity()); - } - } - } - - private JsonObject httpResponseToJson(HttpResponse response) throws OpenViduJavaClientException { - JsonObject json; - try { - json = new Gson().fromJson(EntityUtils.toString(response.getEntity(), "UTF-8"), JsonObject.class); - } catch (JsonSyntaxException | IOException e) { + } catch (URISyntaxException | IOException | InterruptedException e) { throw new OpenViduJavaClientException(e.getMessage(), e.getCause()); } - return json; } protected void setIsBeingRecorded(boolean recording) { diff --git a/openvidu-java-client/src/main/java/io/openvidu/java/client/utils/FormatChecker.java b/openvidu-java-client/src/main/java/io/openvidu/java/client/Utils.java similarity index 65% rename from openvidu-java-client/src/main/java/io/openvidu/java/client/utils/FormatChecker.java rename to openvidu-java-client/src/main/java/io/openvidu/java/client/Utils.java index 7a417b1b..f4ddd029 100644 --- a/openvidu-java-client/src/main/java/io/openvidu/java/client/utils/FormatChecker.java +++ b/openvidu-java-client/src/main/java/io/openvidu/java/client/Utils.java @@ -1,9 +1,24 @@ -package io.openvidu.java.client.utils; +package io.openvidu.java.client; + +import java.net.http.HttpResponse; + +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import com.google.gson.JsonSyntaxException; /** * @hidden */ -public final class FormatChecker { +public class Utils { + + public static JsonObject httpResponseToJson(HttpResponse response) throws OpenViduJavaClientException { + try { + JsonObject json = new Gson().fromJson(response.body(), JsonObject.class); + return json; + } catch (JsonSyntaxException e) { + throw new OpenViduJavaClientException(e.getMessage(), e.getCause()); + } + } public static boolean isAcceptableRecordingResolution(String stringResolution) { // Matches every string with format "AxB", being A and B any number not starting diff --git a/openvidu-java-client/src/test/java/io/openvidu/java/client/test/OpenViduConstructorTest.java b/openvidu-java-client/src/test/java/io/openvidu/java/client/test/OpenViduConstructorTest.java new file mode 100644 index 00000000..3fbe2293 --- /dev/null +++ b/openvidu-java-client/src/test/java/io/openvidu/java/client/test/OpenViduConstructorTest.java @@ -0,0 +1,70 @@ +package io.openvidu.java.client.test; + +import java.net.Authenticator; +import java.net.CookieManager; +import java.net.CookiePolicy; +import java.net.InetSocketAddress; +import java.net.PasswordAuthentication; +import java.net.ProxySelector; +import java.net.http.HttpClient; +import java.net.http.HttpClient.Redirect; +import java.time.Duration; +import java.util.Map; +import java.util.concurrent.Executors; + +import javax.net.ssl.SSLContext; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import io.openvidu.java.client.OpenVidu; + +public class OpenViduConstructorTest { + + @Test + public void wrongHostname() { + RuntimeException thrown = Assertions.assertThrows(RuntimeException.class, () -> { + new OpenVidu("WRONG_URL", "MY_SECRET"); + }); + Assertions.assertEquals("The hostname \"WRONG_URL\" is not a valid URL: no protocol: WRONG_URL", + thrown.getMessage()); + } + + @Test + public void buildWithHttpClientWithoutAuthenticator() { + HttpClient.Builder builder = HttpClient.newBuilder(); + builder.connectTimeout(Duration.ofMillis(10000)); + ProxySelector proxy = ProxySelector.of(new InetSocketAddress("https://my.proxy.hostname/", 4444)); + builder.proxy(proxy); + builder.followRedirects(Redirect.ALWAYS); + SSLContext sslContext = null; + try { + sslContext = SSLContext.getInstance("TLSv1.2"); + sslContext.init(null, null, null); + } catch (Exception e) { + } + builder.sslContext(sslContext); + builder.executor(Executors.newFixedThreadPool(1)); + builder.cookieHandler(new CookieManager(null, CookiePolicy.ACCEPT_ORIGINAL_SERVER)); + OpenVidu OV = new OpenVidu("https://localhost:4443/", "MY_SECRET", builder.build()); + Assertions.assertEquals(30000, OV.getRequestTimeout()); + Assertions.assertTrue(OV.getRequestHeaders().isEmpty()); + OV.setRequestTimeout(5000); + OV.setRequestHeaders(Map.of("header1", "value1", "header2", "value2")); + Assertions.assertEquals(5000, OV.getRequestTimeout()); + Assertions.assertEquals(2, OV.getRequestHeaders().size()); + } + + @Test + public void buildWithHttpClientWithAuthenticator() { + Authenticator authenticator = new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication("OPENVIDUAPP", "secret".toCharArray()); + } + }; + HttpClient.Builder builder = HttpClient.newBuilder().authenticator(authenticator); + new OpenVidu("https://localhost:4443/", "MY_SECRET", builder.build()); + } + +} diff --git a/openvidu-java-client/src/test/java/io/openvidu/java/client/test/FormatCheckerTest.java b/openvidu-java-client/src/test/java/io/openvidu/java/client/test/UtilsFormatCheckerTest.java similarity index 74% rename from openvidu-java-client/src/test/java/io/openvidu/java/client/test/FormatCheckerTest.java rename to openvidu-java-client/src/test/java/io/openvidu/java/client/test/UtilsFormatCheckerTest.java index ebfd2184..07ecbefc 100644 --- a/openvidu-java-client/src/test/java/io/openvidu/java/client/test/FormatCheckerTest.java +++ b/openvidu-java-client/src/test/java/io/openvidu/java/client/test/UtilsFormatCheckerTest.java @@ -6,9 +6,9 @@ import java.util.List; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import io.openvidu.java.client.utils.FormatChecker; +import io.openvidu.java.client.Utils; -public class FormatCheckerTest { +public class UtilsFormatCheckerTest { @Test public void testCustomSessionIdFormat() { @@ -23,9 +23,9 @@ public class FormatCheckerTest { "session-_", "123_session-1"); for (String id : invalidCustomSessionIds) - Assertions.assertFalse(FormatChecker.isValidCustomSessionId(id)); + Assertions.assertFalse(Utils.isValidCustomSessionId(id)); for (String id : validCustomSessionIds) - Assertions.assertTrue(FormatChecker.isValidCustomSessionId(id)); + Assertions.assertTrue(Utils.isValidCustomSessionId(id)); } @Test @@ -37,9 +37,9 @@ public class FormatCheckerTest { List validResolutions = Arrays.asList("1920x1080", "1280x720", "100x1999"); for (String resolution : invalidResolutions) - Assertions.assertFalse(FormatChecker.isAcceptableRecordingResolution(resolution)); + Assertions.assertFalse(Utils.isAcceptableRecordingResolution(resolution)); for (String resolution : validResolutions) - Assertions.assertTrue(FormatChecker.isAcceptableRecordingResolution(resolution)); + Assertions.assertTrue(Utils.isAcceptableRecordingResolution(resolution)); } @Test @@ -50,9 +50,9 @@ public class FormatCheckerTest { List validFramerates = Arrays.asList(1, 2, 30, 60, 119, 120); for (int framerate : invalidFrameRates) - Assertions.assertFalse(FormatChecker.isAcceptableRecordingFrameRate(framerate)); + Assertions.assertFalse(Utils.isAcceptableRecordingFrameRate(framerate)); for (int framerate : validFramerates) - Assertions.assertTrue(FormatChecker.isAcceptableRecordingFrameRate(framerate)); + Assertions.assertTrue(Utils.isAcceptableRecordingFrameRate(framerate)); } } \ No newline at end of file diff --git a/openvidu-node-client/src/OpenVidu.ts b/openvidu-node-client/src/OpenVidu.ts index 36e10e79..7a6aedaa 100644 --- a/openvidu-node-client/src/OpenVidu.ts +++ b/openvidu-node-client/src/OpenVidu.ts @@ -87,10 +87,10 @@ export class OpenVidu { activeSessions: Session[] = []; /** - * @param hostname URL where your instance of OpenVidu Server is up an running. + * @param hostname URL where your OpenVidu deployment is up an running. * It must be the full URL (e.g. `https://12.34.56.78:1234/`) * - * @param secret Secret used on OpenVidu Server initialization + * @param secret Secret configured in your OpenVidu deployment */ constructor(private hostname: string, secret: string) { this.setHostnameAndPort(); diff --git a/openvidu-server/src/main/java/io/openvidu/server/core/SessionManager.java b/openvidu-server/src/main/java/io/openvidu/server/core/SessionManager.java index bd2684cb..83040a84 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/core/SessionManager.java +++ b/openvidu-server/src/main/java/io/openvidu/server/core/SessionManager.java @@ -53,7 +53,6 @@ import io.openvidu.java.client.KurentoOptions; import io.openvidu.java.client.OpenViduRole; import io.openvidu.java.client.Recording; import io.openvidu.java.client.SessionProperties; -import io.openvidu.java.client.utils.FormatChecker; import io.openvidu.server.cdr.CDREventRecordingStatusChanged; import io.openvidu.server.config.OpenviduConfig; import io.openvidu.server.coturn.CoturnCredentialsService; @@ -194,7 +193,7 @@ public abstract class SessionManager { String connectionId); public abstract void stopRtmpIfNecessary(Session session); - + public void onEcho(String participantPrivateId, Integer requestId) { sessionEventsHandler.onEcho(participantPrivateId, requestId); } @@ -338,7 +337,7 @@ public abstract class SessionManager { public Token newToken(Session session, OpenViduRole role, String serverMetadata, boolean record, KurentoOptions kurentoOptions, List customIceServers) throws Exception { - if (!FormatChecker.isServerMetadataFormatCorrect(serverMetadata)) { + if (!io.openvidu.java.client.Utils.isServerMetadataFormatCorrect(serverMetadata)) { log.error("Data invalid format"); throw new OpenViduException(Code.GENERIC_ERROR_CODE, "Data invalid format"); } diff --git a/openvidu-server/src/main/java/io/openvidu/server/rpc/RpcHandler.java b/openvidu-server/src/main/java/io/openvidu/server/rpc/RpcHandler.java index 807f177c..d31bc27e 100644 --- a/openvidu-server/src/main/java/io/openvidu/server/rpc/RpcHandler.java +++ b/openvidu-server/src/main/java/io/openvidu/server/rpc/RpcHandler.java @@ -49,7 +49,6 @@ import io.openvidu.client.internal.ProtocolElements; import io.openvidu.java.client.ConnectionProperties; import io.openvidu.java.client.ConnectionType; import io.openvidu.java.client.OpenViduRole; -import io.openvidu.java.client.utils.FormatChecker; import io.openvidu.server.config.OpenviduBuildInfo; import io.openvidu.server.config.OpenviduConfig; import io.openvidu.server.core.EndReason; @@ -304,7 +303,7 @@ public class RpcHandler extends DefaultJsonRpcHandler { if (tokenObj != null) { String clientMetadata = getStringParam(request, ProtocolElements.JOINROOM_METADATA_PARAM); - if (FormatChecker.isServerMetadataFormatCorrect(clientMetadata)) { + if (io.openvidu.java.client.Utils.isServerMetadataFormatCorrect(clientMetadata)) { // While closing a session users can't join if (session.closingLock.readLock().tryLock()) { diff --git a/openvidu-test-browsers/pom.xml b/openvidu-test-browsers/pom.xml index 8631f253..0fe9197d 100644 --- a/openvidu-test-browsers/pom.xml +++ b/openvidu-test-browsers/pom.xml @@ -102,6 +102,11 @@ java-client ${version.appium}
+ + org.apache.httpcomponents + httpclient + 4.5.14 +
diff --git a/openvidu-test-e2e/pom.xml b/openvidu-test-e2e/pom.xml index cee8cb7a..bc0399f0 100644 --- a/openvidu-test-e2e/pom.xml +++ b/openvidu-test-e2e/pom.xml @@ -112,6 +112,11 @@ java-string-similarity ${version.stringsimilarity} + + org.apache.httpcomponents + httpclient + 4.5.14 + diff --git a/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/OpenViduTestAppE2eTest.java b/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/OpenViduTestAppE2eTest.java index dde2e592..f455b540 100644 --- a/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/OpenViduTestAppE2eTest.java +++ b/openvidu-test-e2e/src/test/java/io/openvidu/test/e2e/OpenViduTestAppE2eTest.java @@ -20,6 +20,13 @@ package io.openvidu.test.e2e; import static org.junit.jupiter.api.Assertions.fail; import java.io.File; +import java.net.Authenticator; +import java.net.PasswordAuthentication; +import java.net.Socket; +import java.net.http.HttpClient; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.Base64; import java.util.Collection; @@ -34,6 +41,12 @@ import java.util.concurrent.TimeoutException; import java.util.function.BiFunction; import java.util.stream.Collectors; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509ExtendedTrustManager; + +import org.apache.commons.lang3.RandomStringUtils; import org.apache.http.HttpStatus; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; @@ -2427,6 +2440,129 @@ public class OpenViduTestAppE2eTest extends AbstractOpenViduTestappE2eTest { gracefullyLeaveParticipants(user, 2); } + @Test + @DisplayName("openvidu-java-client custom HttpClient test") + void openViduJavaClientCustomHttpClientTest() throws Exception { + + // Test all possible combinations: custom Authenticator present and valid, + // present and wrong and no present; in combination with custom Authorization + // header present and valid, present and wrong and no present + + HttpClient.Builder builder = HttpClient.newBuilder(); + SSLContext sslContext; + try { + sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, new TrustManager[] { new X509ExtendedTrustManager() { + public X509Certificate[] getAcceptedIssuers() { + return null; + } + + public void checkClientTrusted(final X509Certificate[] a_certificates, final String a_auth_type) { + } + + public void checkServerTrusted(final X509Certificate[] a_certificates, final String a_auth_type) { + } + + public void checkClientTrusted(final X509Certificate[] a_certificates, final String a_auth_type, + final Socket a_socket) { + } + + public void checkServerTrusted(final X509Certificate[] a_certificates, final String a_auth_type, + final Socket a_socket) { + } + + public void checkClientTrusted(final X509Certificate[] a_certificates, final String a_auth_type, + final SSLEngine a_engine) { + } + + public void checkServerTrusted(final X509Certificate[] a_certificates, final String a_auth_type, + final SSLEngine a_engine) { + } + } }, null); + } catch (KeyManagementException | NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + builder.sslContext(sslContext); + + final String BASIC_AUTH = "Basic " + + Base64.getEncoder().encodeToString(("OPENVIDUAPP:" + OPENVIDU_SECRET).getBytes()); + final String WRONG_SECRET = "WRONG_SECRET_" + RandomStringUtils.randomAlphanumeric(10); + + // 1. No authenticator, no header, 200 + OpenVidu customHttpClientOV1 = new OpenVidu(OPENVIDU_URL, OPENVIDU_SECRET, builder.build()); + customHttpClientOV1.fetch(); + + // 2. No authenticator, wrong header, 401 + customHttpClientOV1.setRequestHeaders(Map.of("Authorization", "WRONG_AUTH_HEADER")); + OpenViduHttpException thrown = Assertions.assertThrows(OpenViduHttpException.class, () -> { + customHttpClientOV1.fetch(); + }); + Assertions.assertEquals(401, thrown.getStatus()); + + // 3. No authenticator and valid header, 200 + customHttpClientOV1.setRequestHeaders(Map.of("Authorization", BASIC_AUTH)); + customHttpClientOV1.fetch(); + + // 4. Wrong authenticator and no header, 401 + builder.authenticator(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication("OPENVIDUAPP", WRONG_SECRET.toCharArray()); + } + }); + OpenVidu customHttpClientOV2 = new OpenVidu(OPENVIDU_URL, OPENVIDU_SECRET, builder.build()); + OpenViduJavaClientException thrown2 = Assertions.assertThrows(OpenViduJavaClientException.class, () -> { + customHttpClientOV2.fetch(); + }); + Assertions.assertTrue(thrown2.getMessage().contains("too many authentication attempts")); + + // 5. Wrong authenticator and wrong header, 401 + customHttpClientOV2.setRequestHeaders(Map.of("Authorization", "WRONG_AUTH_HEADER")); + thrown2 = Assertions.assertThrows(OpenViduJavaClientException.class, () -> { + customHttpClientOV2.fetch(); + }); + Assertions.assertTrue(thrown2.getMessage().contains("too many authentication attempts")); + + // 6. Wrong authenticator and valid header, 401 + customHttpClientOV2.setRequestHeaders(Map.of("Authorization", BASIC_AUTH)); + thrown2 = Assertions.assertThrows(OpenViduJavaClientException.class, () -> { + customHttpClientOV2.fetch(); + }); + Assertions.assertTrue(thrown2.getMessage().contains("too many authentication attempts")); + + // 7. Valid authenticator and no header, 200 + builder.authenticator(new Authenticator() { + @Override + protected PasswordAuthentication getPasswordAuthentication() { + return new PasswordAuthentication("OPENVIDUAPP", OPENVIDU_SECRET.toCharArray()); + } + }); + OpenVidu customHttpClientOV3 = new OpenVidu(OPENVIDU_URL, OPENVIDU_SECRET, builder.build()); + customHttpClientOV3.fetch(); + + // 8. Valid authenticator and wrong header, 200 + customHttpClientOV3.setRequestHeaders(Map.of("Authorization", "WRONG_AUTH_HEADER")); + customHttpClientOV3.fetch(); + + // 9. Valid authenticator and valid header, 200 + customHttpClientOV3.setRequestHeaders(Map.of("Authorization", BASIC_AUTH)); + customHttpClientOV3.fetch(); + + // 10. Wrong secret, valid authenticator, no header, 200 + OpenVidu customHttpClientOV4 = new OpenVidu(OPENVIDU_URL, WRONG_SECRET, builder.build()); + customHttpClientOV4.fetch(); + + // 11. Wrong secret, valid authenticator, wrong header, 200 + customHttpClientOV4.setRequestHeaders(Map.of("Authorization", "WRONG_AUTH_HEADER")); + customHttpClientOV4.fetch(); + + // 12. Wrong secret, no authenticator, valid header, 200 + builder = HttpClient.newBuilder().sslContext(sslContext); + customHttpClientOV4 = new OpenVidu(OPENVIDU_URL, WRONG_SECRET, builder.build()); + customHttpClientOV4.setRequestHeaders(Map.of("Authorization", BASIC_AUTH)); + customHttpClientOV4.fetch(); + } + @Test @DisplayName("openvidu-java-client test") void openViduJavaClientTest() throws Exception {