[loxone] Support for HTTPS websocket connections. (#10185)
Signed-off-by: Pawel Pieczul <pieczul@gmail.com>
This commit is contained in:
parent
e86cc6b219
commit
3a19b29662
|
@ -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
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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/")) {
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -39,6 +39,8 @@ public class LxResponse {
|
|||
public class LxResponseCfgApi {
|
||||
public String snr;
|
||||
public String version;
|
||||
public String key;
|
||||
public Integer httpsStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -39,10 +39,15 @@
|
|||
<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>
|
||||
|
@ -71,6 +76,18 @@
|
|||
<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>
|
||||
|
|
Loading…
Reference in New Issue