]> git.basschouten.com Git - openhab-addons.git/commitdiff
[homematic] Add Authentication (#16196)
authorChristian Kittel <EvilPingu@users.noreply.github.com>
Mon, 19 Feb 2024 00:09:05 +0000 (01:09 +0100)
committerGitHub <noreply@github.com>
Mon, 19 Feb 2024 00:09:05 +0000 (01:09 +0100)
* Add Authentication

---------

Signed-off-by: Christian Kittel <ckittel@gmx.de>
bundles/org.openhab.binding.homematic/README.md
bundles/org.openhab.binding.homematic/src/main/java/org/openhab/binding/homematic/internal/common/AuthenticationHandler.java [new file with mode: 0644]
bundles/org.openhab.binding.homematic/src/main/java/org/openhab/binding/homematic/internal/common/HomematicConfig.java
bundles/org.openhab.binding.homematic/src/main/java/org/openhab/binding/homematic/internal/communicator/AbstractHomematicGateway.java
bundles/org.openhab.binding.homematic/src/main/java/org/openhab/binding/homematic/internal/communicator/CcuGateway.java
bundles/org.openhab.binding.homematic/src/main/java/org/openhab/binding/homematic/internal/communicator/HomematicGatewayFactory.java
bundles/org.openhab.binding.homematic/src/main/java/org/openhab/binding/homematic/internal/communicator/client/XmlRpcClient.java
bundles/org.openhab.binding.homematic/src/main/java/org/openhab/binding/homematic/internal/handler/HomematicBridgeHandler.java
bundles/org.openhab.binding.homematic/src/main/resources/OH-INF/i18n/homematic.properties
bundles/org.openhab.binding.homematic/src/main/resources/OH-INF/thing/bridge.xml

index 07f447eb48e6ed1572514d2e98a9a1ee3cc6e0b2..1849f809ad4e8c058708650841825e8a98abbeb2 100644 (file)
@@ -14,15 +14,28 @@ When the option "Restricted access" is used, some ports have to be added to the
 2000;
 2001;
 2010;
+8181;
 8701;
 9292;
 ```
 
 Also the IP of the device running openHAB has to be set to the list of "IP addresses for restricted access".
 
-Also under `Home page > Settings > Control panel` with the menu `Security` the option `Authentication` has to be disabled as the binding does not support the configuration of `username` and `password`for the XML-RPC API.
+Also under `Home page > Settings > Control panel` with the menu `Security` the option `Authentication` has to be disabled if the option 'useAuthentication' is not set.
+This option may be enabled if the option 'useAuthentication' is set and BIN-RPC is not used.
+In this case, a user and password must be created.
+This can be done under `Home page > Settings > Control panel` with the menu `User management`.
+This can be done under `Home page > Settings > Control Panel` in the `User Management` menu.
+The new user should have the following configuration:
 
-If this is not done the binding will not be able to connect to the CCU and the CCU Thing will stay uninitialized and sets a timeout exception:
+- User name - button for login: No
+- Permission level: User
+- Expert mode not visible: Yes
+- Automatically confirm the device message: Yes
+
+The user and password must then be entered in the 'Username' and 'Password' settings.
+
+If this is not done the binding will not be able to connect to the CCU and the CCU Thing will stay uninitialized and sets a timeout exception or a authentication error
 
 ```text
 xxx-xx-xx xx:xx:xx.xxx [hingStatusInfoChangedEvent] - - 'homematic:bridge:xxx' changed from INITIALIZING to OFFLINE (COMMUNICATION_ERROR): java.net.SocketTimeoutException: Connect Timeout
@@ -179,6 +192,15 @@ Due to the factory reset, the device will also be unpaired from the gateway, eve
   If a large number of devices are connected to the gateway, the default buffersize of 2048 kB may be too small for communication with the gateway.
   In this case, e.g. the discovery fails.
   With this setting the buffer size can be adjusted. The value is specified in kB.
+  
+- **useAuthentication**
+Username and password are send to the gateway to authenticate the access to the gateway.
+
+- **username**
+Username for Authentication to the gateway.
+
+- **password**
+Password for Authentication to the gateway.
 
 The syntax for a bridge is:
 
diff --git a/bundles/org.openhab.binding.homematic/src/main/java/org/openhab/binding/homematic/internal/common/AuthenticationHandler.java b/bundles/org.openhab.binding.homematic/src/main/java/org/openhab/binding/homematic/internal/common/AuthenticationHandler.java
new file mode 100644 (file)
index 0000000..33152e3
--- /dev/null
@@ -0,0 +1,53 @@
+/**
+ * Copyright (c) 2010-2024 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.homematic.internal.common;
+
+import java.util.Base64;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.jetty.client.api.Request;
+import org.eclipse.jetty.http.HttpHeader;
+import org.openhab.core.i18n.ConfigurationException;
+
+/**
+ * Handles the authentication to Homematic server.
+ *
+ * @author Christian Kittel
+ */
+@NonNullByDefault
+public class AuthenticationHandler {
+
+    private Boolean useAuthentication;
+    private @Nullable String authValue;
+
+    public AuthenticationHandler(HomematicConfig config) throws ConfigurationException {
+        this.useAuthentication = config.getUseAuthentication();
+        if (!useAuthentication) {
+            return;
+        }
+
+        if (config.getPassword() == null || config.getUserName() == null) {
+            throw new ConfigurationException("Username or password missing");
+        }
+        this.authValue = "Basic "
+                + Base64.getEncoder().encodeToString((config.getUserName() + ":" + config.getPassword()).getBytes());
+    }
+
+    /**
+     * Add or remove the basic auth credentials th the request if needed.
+     */
+    public Request updateAuthenticationInformation(final Request request) {
+        return useAuthentication ? request.header(HttpHeader.AUTHORIZATION, authValue) : request;
+    }
+}
index 9624a855c76389eeeaa635cd6f24aed4872ac4e6..1f5cb73b2b5b7e1ef57160592add47c0a8714f5d 100644 (file)
@@ -60,6 +60,10 @@ public class HomematicConfig {
     private HmGatewayInfo gatewayInfo;
     private int callbackRegTimeout;
 
+    private boolean useAuthentication;
+    private String userName;
+    private String password;
+
     /**
      * Returns the Homematic gateway address.
      */
@@ -272,6 +276,48 @@ public class HomematicConfig {
         return getRpcPort(channel.getDevice().getHmInterface());
     }
 
+    /**
+     * Returns true if authorization for the gateway has to be used; otherwise false
+     */
+    public boolean getUseAuthentication() {
+        return useAuthentication;
+    }
+
+    /**
+     * Sets if authorization for the gateway has to be used
+     */
+    public void setUseAuthentication(Boolean useAuthentication) {
+        this.useAuthentication = useAuthentication;
+    }
+
+    /**
+     * Returns the user name for authorize against the gateway
+     */
+    public String getUserName() {
+        return userName;
+    }
+
+    /**
+     * Sets the user name for authorize against the gateway
+     */
+    public void setUserName(String userName) {
+        this.userName = userName;
+    }
+
+    /**
+     * Returns the password for authorize against the gateway
+     */
+    public String getPassword() {
+        return password;
+    }
+
+    /**
+     * Sets the password for authorize against the gateway
+     */
+    public void setPassword(String password) {
+        this.password = password;
+    }
+
     /**
      * Returns the Homematic gateway port of the interfaces.
      */
index 64023f8e8189c1f4ecc303031ee0be02adc826fe..7210e024352f1eb3e8117d0f53350de998c7a9de 100644 (file)
@@ -12,7 +12,7 @@
  */
 package org.openhab.binding.homematic.internal.communicator;
 
-import static org.openhab.binding.homematic.internal.HomematicBindingConstants.*;
+import static org.openhab.binding.homematic.internal.HomematicBindingConstants.GATEWAY_POOL_NAME;
 import static org.openhab.binding.homematic.internal.misc.HomematicConstants.*;
 
 import java.io.IOException;
index 7f43335a6f7e25a286daa3cef95dd13d5f51c2e3..05d7d620872975c86a7edf76335f5c192150268d 100644 (file)
@@ -22,10 +22,12 @@ import java.util.Map;
 import java.util.Objects;
 import java.util.concurrent.TimeUnit;
 
+import org.eclipse.jdt.annotation.NonNull;
 import org.eclipse.jetty.client.HttpClient;
 import org.eclipse.jetty.client.api.ContentResponse;
 import org.eclipse.jetty.client.util.StringContentProvider;
 import org.eclipse.jetty.http.HttpHeader;
+import org.openhab.binding.homematic.internal.common.AuthenticationHandler;
 import org.openhab.binding.homematic.internal.common.HomematicConfig;
 import org.openhab.binding.homematic.internal.communicator.client.UnknownParameterSetException;
 import org.openhab.binding.homematic.internal.communicator.client.UnknownRpcFailureException;
@@ -41,6 +43,7 @@ import org.openhab.binding.homematic.internal.model.HmResult;
 import org.openhab.binding.homematic.internal.model.TclScript;
 import org.openhab.binding.homematic.internal.model.TclScriptDataList;
 import org.openhab.binding.homematic.internal.model.TclScriptList;
+import org.openhab.core.i18n.ConfigurationException;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.FrameworkUtil;
 import org.slf4j.Logger;
@@ -60,10 +63,14 @@ public class CcuGateway extends AbstractHomematicGateway {
     private Map<String, String> tclregaScripts;
     private XStream xStream = new XStream(new StaxDriver());
 
+    private @NonNull AuthenticationHandler authenticationHandler;
+
     protected CcuGateway(String id, HomematicConfig config, HomematicGatewayAdapter gatewayAdapter,
-            HttpClient httpClient) {
+            HttpClient httpClient) throws IOException, ConfigurationException {
         super(id, config, gatewayAdapter, httpClient);
 
+        this.authenticationHandler = new AuthenticationHandler(config);
+
         xStream.allowTypesByWildcard(new String[] { HmDevice.class.getPackageName() + ".**" });
         xStream.setClassLoader(CcuGateway.class.getClassLoader());
         xStream.autodetectAnnotations(true);
@@ -211,9 +218,9 @@ public class CcuGateway extends AbstractHomematicGateway {
             }
 
             StringContentProvider content = new StringContentProvider(script, config.getEncoding());
-            ContentResponse response = httpClient.POST(config.getTclRegaUrl()).content(content)
-                    .timeout(config.getTimeout(), TimeUnit.SECONDS)
-                    .header(HttpHeader.CONTENT_TYPE, "text/plain;charset=" + config.getEncoding()).send();
+            ContentResponse response = authenticationHandler.updateAuthenticationInformation(httpClient
+                    .POST(config.getTclRegaUrl()).content(content).timeout(config.getTimeout(), TimeUnit.SECONDS)
+                    .header(HttpHeader.CONTENT_TYPE, "text/plain;charset=" + config.getEncoding())).send();
 
             String result = new String(response.getContent(), config.getEncoding());
             int lastPos = result.lastIndexOf("<xml><exec>");
index 9f2271ee51fa15d05df24820077f245a3b624425..d50def0c80cc66adafa3f9085c14b81800f2bcff 100644 (file)
@@ -18,6 +18,7 @@ import org.eclipse.jetty.client.HttpClient;
 import org.openhab.binding.homematic.internal.common.HomematicConfig;
 import org.openhab.binding.homematic.internal.communicator.client.RpcClient;
 import org.openhab.binding.homematic.internal.communicator.client.XmlRpcClient;
+import org.openhab.core.i18n.ConfigurationException;
 
 /**
  * Factory which evaluates the type of the Homematic gateway and instantiates the appropriate class.
@@ -30,7 +31,7 @@ public class HomematicGatewayFactory {
      * Creates the HomematicGateway.
      */
     public static HomematicGateway createGateway(String id, HomematicConfig config,
-            HomematicGatewayAdapter gatewayAdapter, HttpClient httpClient) throws IOException {
+            HomematicGatewayAdapter gatewayAdapter, HttpClient httpClient) throws IOException, ConfigurationException {
         loadGatewayInfo(config, id, httpClient);
         if (config.getGatewayInfo().isCCU()) {
             return new CcuGateway(id, config, gatewayAdapter, httpClient);
index 3ea6d35f8c9dfb020ef0e1de340b966f171394e0..8776acc95e829223481ec875cc80226fe4ad1ea6 100644 (file)
@@ -14,6 +14,8 @@ package org.openhab.binding.homematic.internal.communicator.client;
 
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
@@ -26,11 +28,14 @@ import org.eclipse.jetty.client.api.Request;
 import org.eclipse.jetty.client.util.BytesContentProvider;
 import org.eclipse.jetty.client.util.FutureResponseListener;
 import org.eclipse.jetty.http.HttpHeader;
+import org.eclipse.jetty.http.HttpStatus;
+import org.openhab.binding.homematic.internal.common.AuthenticationHandler;
 import org.openhab.binding.homematic.internal.common.HomematicConfig;
 import org.openhab.binding.homematic.internal.communicator.message.RpcRequest;
 import org.openhab.binding.homematic.internal.communicator.message.XmlRpcRequest;
 import org.openhab.binding.homematic.internal.communicator.message.XmlRpcResponse;
 import org.openhab.binding.homematic.internal.communicator.parser.RpcResponseParser;
+import org.openhab.core.i18n.ConfigurationException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.xml.sax.SAXException;
@@ -43,8 +48,9 @@ import org.xml.sax.SAXException;
 public class XmlRpcClient extends RpcClient<String> {
     private final Logger logger = LoggerFactory.getLogger(XmlRpcClient.class);
     private HttpClient httpClient;
+    private AuthenticationHandler authenticationHandler;
 
-    public XmlRpcClient(HomematicConfig config, HttpClient httpClient) throws IOException {
+    public XmlRpcClient(HomematicConfig config, HttpClient httpClient) throws IOException, ConfigurationException {
         super(config);
         this.httpClient = httpClient;
     }
@@ -103,11 +109,22 @@ public class XmlRpcClient extends RpcClient<String> {
             if (port == config.getGroupPort()) {
                 url += "/groups";
             }
-            Request req = httpClient.POST(url).content(content).timeout(config.getTimeout(), TimeUnit.SECONDS)
-                    .header(HttpHeader.CONTENT_TYPE, "text/xml;charset=" + config.getEncoding());
+            if (authenticationHandler == null) {
+                authenticationHandler = new AuthenticationHandler(config);
+            }
+
+            Request req = authenticationHandler.updateAuthenticationInformation(
+                    httpClient.POST(new URI(url)).content(content).timeout(config.getTimeout(), TimeUnit.SECONDS)
+                            .header(HttpHeader.CONTENT_TYPE, "text/xml;charset=" + config.getEncoding()));
+
             FutureResponseListener listener = new FutureResponseListener(req, config.getBufferSize() * 1024);
             req.send(listener);
             ContentResponse response = listener.get(config.getTimeout(), TimeUnit.SECONDS);
+
+            if (response.getStatus() == HttpStatus.UNAUTHORIZED_401) {
+                throw new IOException("Access to Homematic gateway unauthorized");
+            }
+
             ret = response.getContent();
             if (ret == null || ret.length == 0) {
                 throw new IOException("Received no data from the Homematic gateway");
@@ -116,7 +133,8 @@ public class XmlRpcClient extends RpcClient<String> {
                 String result = new String(ret, config.getEncoding());
                 logger.trace("Client XmlRpcResponse (port {}):\n{}", port, result);
             }
-        } catch (InterruptedException | ExecutionException | TimeoutException | IllegalArgumentException e) {
+        } catch (InterruptedException | ExecutionException | TimeoutException | IllegalArgumentException
+                | URISyntaxException | ConfigurationException e) {
             throw new IOException(e);
         }
         return ret;
index 8d4f29fe9d3bfa81122e3ad57919c6f8b6e77302..5e0e358d1d4a386fd9423ff45ba67a6abf97b594 100644 (file)
@@ -36,6 +36,7 @@ import org.openhab.binding.homematic.internal.model.HmDevice;
 import org.openhab.binding.homematic.internal.model.HmGatewayInfo;
 import org.openhab.binding.homematic.internal.type.HomematicTypeGenerator;
 import org.openhab.binding.homematic.internal.type.UidUtils;
+import org.openhab.core.i18n.ConfigurationException;
 import org.openhab.core.library.types.DecimalType;
 import org.openhab.core.thing.Bridge;
 import org.openhab.core.thing.Channel;
@@ -132,6 +133,9 @@ public class HomematicBridgeHandler extends BaseBridgeHandler implements Homemat
                         ex.getMessage(), ex);
                 disposeInternal();
                 scheduleReinitialize();
+            } catch (ConfigurationException ex) {
+                updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, ex.getMessage());
+                disposeInternal();
             }
         }
     }
index 207b8dbd7ec5fa516798b7d51a165292b0db2170..a78f4d6b4815bf940a972ba08d3725b6cd337563 100644 (file)
@@ -37,6 +37,8 @@ thing-type.config.homematic.bridge.hmIpPort.label = HMIP Port
 thing-type.config.homematic.bridge.hmIpPort.description = The port number of the Homematic IP daemon
 thing-type.config.homematic.bridge.installModeDuration.label = Install Mode Duration
 thing-type.config.homematic.bridge.installModeDuration.description = Time in seconds that the controller will be in install mode when a device discovery is initiated
+thing-type.config.homematic.bridge.password.label = Password
+thing-type.config.homematic.bridge.password.description = Password for accessing the gateway if authenticaton is required.
 thing-type.config.homematic.bridge.rfPort.label = RF Port
 thing-type.config.homematic.bridge.rfPort.description = The port number of the RF daemon
 thing-type.config.homematic.bridge.socketMaxAlive.label = Socket MaxAlive
@@ -45,6 +47,10 @@ thing-type.config.homematic.bridge.timeout.label = Timeout
 thing-type.config.homematic.bridge.timeout.description = The timeout in seconds for connections to a Homematic gateway
 thing-type.config.homematic.bridge.unpairOnDeletion.label = Unpair Devices On Deletion
 thing-type.config.homematic.bridge.unpairOnDeletion.description = If set to true, devices are unpaired from the gateway when their corresponding things are removed. The option "factoryResetOnDeletion" also unpairs a device, so in order to avoid unpairing on deletion, both options need to be set to false!
+thing-type.config.homematic.bridge.useAuthentication.label = Use authentication
+thing-type.config.homematic.bridge.useAuthentication.description = Use authentication to access the gateway. If set to true, username and password is required.
+thing-type.config.homematic.bridge.userName.label = User name
+thing-type.config.homematic.bridge.userName.description = User name for accessing the gateway if authenticaton is required.
 thing-type.config.homematic.bridge.wiredPort.label = Wired Port
 thing-type.config.homematic.bridge.wiredPort.description = The port number of the HS485 daemon
 thing-type.config.homematic.bridge.xmlCallbackPort.label = XML-RPC Callback Port
index 343098ea1d90efae7c5ddd0e01549982e849f03a..42537ce61c82d73ba3427c7cf1a482bfd9f95a3f 100644 (file)
                                <default>2048</default>
                                <advanced>true</advanced>
                        </parameter>
+                       <parameter name="useAuthentication" type="boolean">
+                               <label>Use authentication</label>
+                               <description>Use authentication to access the gateway. If set to true, username and password is required.</description>
+                               <advanced>true</advanced>
+                               <default>false</default>
+                       </parameter>
+                       <parameter name="userName" type="text">
+                               <label>User name</label>
+                               <description>User name for accessing the gateway if authenticaton is required.</description>
+                               <advanced>true</advanced>
+                       </parameter>
+                       <parameter name="password" type="text">
+                               <label>Password</label>
+                               <description>Password for accessing the gateway if authenticaton is required.</description>
+                               <advanced>true</advanced>
+                               <context>password</context>
+                       </parameter>
                </config-description>
        </bridge-type>