]> git.basschouten.com Git - openhab-addons.git/commitdiff
[loxone] Support for HTTPS websocket connections. (#10185)
authorPawel Pieczul <pieczul@gmail.com>
Thu, 25 Feb 2021 21:46:04 +0000 (22:46 +0100)
committerGitHub <noreply@github.com>
Thu, 25 Feb 2021 21:46:04 +0000 (13:46 -0800)
Signed-off-by: Pawel Pieczul <pieczul@gmail.com>
bundles/org.openhab.binding.loxone/README.md
bundles/org.openhab.binding.loxone/src/main/java/org/openhab/binding/loxone/internal/LxBindingConfiguration.java
bundles/org.openhab.binding.loxone/src/main/java/org/openhab/binding/loxone/internal/LxServerHandler.java
bundles/org.openhab.binding.loxone/src/main/java/org/openhab/binding/loxone/internal/LxWebSocket.java
bundles/org.openhab.binding.loxone/src/main/java/org/openhab/binding/loxone/internal/controls/LxControl.java
bundles/org.openhab.binding.loxone/src/main/java/org/openhab/binding/loxone/internal/types/LxResponse.java
bundles/org.openhab.binding.loxone/src/main/resources/OH-INF/thing/thing-types.xml

index 37911c24bfa62388ca52216ac6c35b5840eef3ca..37ab59094e673e8db437af800e1a5d51ff1fcde4 100644 (file)
@@ -96,6 +96,9 @@ The acquired token will remain active for several weeks following the last succe
 
 In case a websocket connection to the Miniserver remains active for the whole duration of the token's life span, the binding will refresh the token one day before token expiration, without the need of providing the password.
 
+In case of connecting to Generation 2 Miniservers, it is possible to establish a secure WebSocket connection over HTTPS protocol. Binding will automatically detect if HTTPS connection is available and will use it. In that case, commands sent to the Miniserver will not be additionally encrypted. When HTTPS is not available, binding will use unsecure HTTP connection and will encrypt each command.
+
+It is possible to override the communication protocol by setting `webSocketType` configuration parameter. Setting it to 1 will force to always establish HTTPS connection. Setting it to 2 will force to always establish HTTP connection. Default value of 0 means the binding will determine the right protocol in the runtime.
 
 A method to enable unrestricted security policy depends on the JRE version and vendor, some examples can be found [here](https://www.petefreitag.com/item/844.cfm) and [here](https://stackoverflow.com/questions/41580489/how-to-install-unlimited-strength-jurisdiction-policy-files).
 
@@ -195,9 +198,10 @@ To define a parameter value in a .things file, please refer to it by parameter's
 
 ### Security
 
-| ID           | Name                  | Values                                          | Default      | Description                                           |
-|--------------|-----------------------|-------------------------------------------------|--------------|-------------------------------------------------------|
-| `authMethod` | Authentication method | 0: Automatic<br>1: Hash-based<br>2: Token-based | 0: Automatic | A method used to authenticate user in the Miniserver. |
+| ID              | Name                  | Values                                          | Default      | Description                                           |
+|-----------------|-----------------------|-------------------------------------------------|--------------|-------------------------------------------------------|
+| `authMethod`    | Authentication method | 0: Automatic<br>1: Hash-based<br>2: Token-based | 0: Automatic | A method used to authenticate user in the Miniserver. |
+| `webSocketType` | WebSocket protocol    | 0: Automatic<br>1: Force HTTPS<br>2: Force HTTP | 0: Automatic | Communication protocol used for WebSocket connection. |
 
 ### Timeouts
 
index 1cebb8770567a7defbd9a62ae5dd55478cf90b1a..5d817bd2f4021cb5c7c96b832014d51a263f8a1d 100644 (file)
@@ -24,9 +24,13 @@ public class LxBindingConfiguration {
      */
     public String host;
     /**
-     * Port of web service of the Miniserver
+     * Port of HTTP web service of the Miniserver
      */
     public int port;
+    /**
+     * Port of HTTPS web service of the Miniserver
+     */
+    public int httpsPort;
     /**
      * User name used to log into the Miniserver
      */
@@ -76,4 +80,8 @@ public class LxBindingConfiguration {
      * Authentication method (0-auto, 1-hash, 2-token)
      */
     public int authMethod;
+    /**
+     * WebSocket connection type (0-auto, 1-HTTPS, 2-HTTP)
+     */
+    public int webSocketType;
 }
index aa6b9d58dedc0c91a669c1f213f21a91b39b48a4..17517e2b3faa50a252eaecfd8fadb6d1d44fa781 100644 (file)
@@ -32,6 +32,7 @@ import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 
+import org.eclipse.jetty.util.ssl.SslContextFactory;
 import org.eclipse.jetty.util.thread.QueuedThreadPool;
 import org.eclipse.jetty.websocket.client.ClientUpgradeRequest;
 import org.eclipse.jetty.websocket.client.WebSocketClient;
@@ -72,7 +73,7 @@ import com.google.gson.GsonBuilder;
 public class LxServerHandler extends BaseThingHandler implements LxServerHandlerApi {
 
     private static final String SOCKET_URL = "/ws/rfc6455";
-    private static final String CMD_CFG_API = "jdev/cfg/api";
+    private static final String CMD_CFG_API = "jdev/cfg/apiKey";
 
     private static final Gson GSON;
 
@@ -139,6 +140,7 @@ public class LxServerHandler extends BaseThingHandler implements LxServerHandler
 
     @Override
     public void handleCommand(ChannelUID channelUID, Command command) {
+        logger.debug("[{}] Handle command: channelUID={}, command={}", debugId, channelUID, command);
         if (command instanceof RefreshType) {
             updateChannelState(channelUID);
             return;
@@ -146,6 +148,8 @@ public class LxServerHandler extends BaseThingHandler implements LxServerHandler
         try {
             LxControl control = channels.get(channelUID);
             if (control != null) {
+                logger.debug("[{}] Dispatching command to control UUID={}, name={}", debugId, control.getUuid(),
+                        control.getName());
                 control.handleCommand(channelUID, command);
             } else {
                 logger.error("[{}] Received command {} for unknown control.", debugId, command);
@@ -182,7 +186,7 @@ public class LxServerHandler extends BaseThingHandler implements LxServerHandler
             jettyThreadPool.setDaemon(true);
 
             socket = new LxWebSocket(debugId, this, bindingConfig, host);
-            wsClient = new WebSocketClient();
+            wsClient = new WebSocketClient(new SslContextFactory.Client(true));
             wsClient.setExecutor(jettyThreadPool);
             if (debugId > 1) {
                 reconnectDelay.set(0);
@@ -478,8 +482,16 @@ public class LxServerHandler extends BaseThingHandler implements LxServerHandler
         Map<LxUuid, LxState> perStateUuid = states.get(update.getUuid());
         if (perStateUuid != null) {
             perStateUuid.forEach((controlUuid, state) -> {
+                logger.debug("[{}] State update (UUID={}, value={}) dispatched to control UUID={}, state name={}",
+                        debugId, update.getUuid(), update.getValue(), controlUuid, state.getName());
+
                 state.setStateValue(update.getValue());
             });
+            if (perStateUuid.size() == 0) {
+                logger.debug("[{}] State update UUID={} has empty controls table", debugId, update.getUuid());
+            }
+        } else {
+            logger.debug("[{}] State update UUID={} has no controls table", debugId, update.getUuid());
         }
     }
 
@@ -553,16 +565,36 @@ public class LxServerHandler extends BaseThingHandler implements LxServerHandler
          * Try to read CfgApi structure from the miniserver. It contains serial number and firmware version. If it can't
          * be read this is not a fatal issue, we will assume most recent version running.
          */
+        boolean httpsCapable = false;
         String message = socket.httpGet(CMD_CFG_API);
         if (message != null) {
             LxResponse resp = socket.getResponse(message);
             if (resp != null) {
-                socket.setFwVersion(GSON.fromJson(resp.getValueAsString(), LxResponse.LxResponseCfgApi.class).version);
+                LxResponse.LxResponseCfgApi apiResp = GSON.fromJson(resp.getValueAsString(),
+                        LxResponse.LxResponseCfgApi.class);
+                if (apiResp != null) {
+                    socket.setFwVersion(apiResp.version);
+                    httpsCapable = apiResp.httpsStatus != null && apiResp.httpsStatus == 1;
+                }
             }
         } else {
             logger.debug("[{}] Http get failed for API config request.", debugId);
         }
 
+        switch (bindingConfig.webSocketType) {
+            case 0:
+                // keep automatically determined option
+                break;
+            case 1:
+                logger.debug("[{}] Forcing HTTPS websocket connection.", debugId);
+                httpsCapable = true;
+                break;
+            case 2:
+                logger.debug("[{}] Forcing HTTP websocket connection.", debugId);
+                httpsCapable = false;
+                break;
+        }
+
         try {
             wsClient.start();
 
@@ -570,7 +602,14 @@ public class LxServerHandler extends BaseThingHandler implements LxServerHandler
             // without this zero timeout, jetty will wait 30 seconds for stopping the client to eventually fail
             // with the timeout it is immediate and all threads end correctly
             jettyThreadPool.setStopTimeout(0);
-            URI target = new URI("ws://" + host.getHostAddress() + ":" + bindingConfig.port + SOCKET_URL);
+            URI target;
+            if (httpsCapable) {
+                target = new URI("wss://" + host.getHostAddress() + ":" + bindingConfig.httpsPort + SOCKET_URL);
+                socket.setHttps(true);
+            } else {
+                target = new URI("ws://" + host.getHostAddress() + ":" + bindingConfig.port + SOCKET_URL);
+                socket.setHttps(false);
+            }
             ClientUpgradeRequest request = new ClientUpgradeRequest();
             request.setSubProtocols("remotecontrol");
 
index 79867bd910adc5641ea2afe0f8254ba37c00386f..298e53f1545798515e6fc2fdb8808913b4574d2a 100644 (file)
@@ -78,6 +78,7 @@ public class LxWebSocket {
 
     private Session session;
     private String fwVersion;
+    private boolean httpsSession = false;
     private ScheduledFuture<?> timeout;
     private LxWsBinaryHeader header;
     private LxWsSecurity security;
@@ -455,9 +456,20 @@ public class LxWebSocket {
      * @param fwVersion Miniserver firmware version
      */
     void setFwVersion(String fwVersion) {
+        logger.debug("[{}] Firmware version: {}", debugId, fwVersion);
         this.fwVersion = fwVersion;
     }
 
+    /**
+     * Sets information if session is over HTTPS or HTTP protocol
+     *
+     * @param httpsSession true when HTTPS session
+     */
+    void setHttps(boolean httpsSession) {
+        logger.debug("[{}] HTTPS session: {}", debugId, httpsSession);
+        this.httpsSession = httpsSession;
+    }
+
     /**
      * Start a timer to wait for a Miniserver response to an action sent from the binding.
      * When timer expires, connection is removed and server error is reported. Further connection attempt can be made
@@ -536,7 +548,7 @@ public class LxWebSocket {
         try {
             if (session != null) {
                 String encrypted;
-                if (encrypt) {
+                if (encrypt && !httpsSession) {
                     encrypted = security.encrypt(command);
                     logger.debug("[{}] Sending encrypted string: {}", debugId, command);
                     logger.debug("[{}] Encrypted: {}", debugId, encrypted);
@@ -580,7 +592,9 @@ public class LxWebSocket {
         }
         logger.debug("[{}] Response: {}", debugId, message.trim());
         String control = resp.getCommand().trim();
-        control = security.decryptControl(control);
+        if (!httpsSession) {
+            control = security.decryptControl(control);
+        }
         // for some reason the responses to some commands starting with jdev begin with dev, not jdev
         // this seems to be a bug in the Miniserver
         if (control.startsWith("dev/")) {
index 2533b90f8ce385370ad2f9c4cc5f00e65ecb8fe0..f16c04596fa3d8a8852311feec60c1fe1b851141 100644 (file)
@@ -264,6 +264,8 @@ public class LxControl {
         Callbacks c = callbacks.get(channelId);
         if (c != null && c.commandCallback != null) {
             c.commandCallback.handleCommand(command);
+        } else {
+            logger.debug("Control UUID={} has no command handler", getUuid());
         }
     }
 
index 45af9c7da117ad5e32a906a0eafa4eebb447434a..c9e4a37c6223d77a62be60c625764bd1c1aa21a2 100644 (file)
@@ -39,6 +39,8 @@ public class LxResponse {
     public class LxResponseCfgApi {
         public String snr;
         public String version;
+        public String key;
+        public Integer httpsStatus;
     }
 
     /**
index 52ef9ccf6c210d22671a241f93c7ad3adedf69eb..72330526c8f9fcc14d4889d816ff807cb7e17f7a 100644 (file)
                                <description>Host address or IP of the Loxone Miniserver</description>
                        </parameter>
                        <parameter name="port" type="integer" min="1" max="65535" groupName="miniserver">
-                               <label>Port</label>
-                               <description>Web interface port of the Loxone Miniserver</description>
+                               <label>HTTP Port</label>
+                               <description>HTTP Web interface port of the Loxone Miniserver</description>
                                <default>80</default>
                        </parameter>
+                       <parameter name="httpsPort" type="integer" min="1" max="65535" groupName="miniserver">
+                               <label>HTTPS Port</label>
+                               <description>HTTPS Web interface port of the Loxone Miniserver</description>
+                               <default>443</default>
+                       </parameter>
 
                        <parameter name="authMethod" type="integer" min="0" max="2" groupName="security">
                                <label>Authorization Method</label>
                                <advanced>true</advanced>
                        </parameter>
 
+                       <parameter name="webSocketType" type="integer" min="0" max="2" groupName="security">
+                               <label>WebSocket Connection Type</label>
+                               <description>Protocol used to communicate over WebSocket to the Miniserver</description>
+                               <default>0</default>
+                               <options>
+                                       <option value="0">Automatic</option>
+                                       <option value="1">Force HTTPS</option>
+                                       <option value="2">Force HTTP</option>
+                               </options>
+                               <limitToOptions>true</limitToOptions>
+                               <advanced>true</advanced>
+                       </parameter>
                        <parameter name="firstConDelay" type="integer" min="0" max="120" groupName="timeouts">
                                <label>First Connection Delay</label>
                                <description>Time between binding initialization and first connection attempt (seconds, 0-120)</description>