Skip to content

Commit

Permalink
[homematic] Add Authentication (openhab#16196)
Browse files Browse the repository at this point in the history
* Add Authentication

---------

Signed-off-by: Christian Kittel <ckittel@gmx.de>
Signed-off-by: Jørgen Austvik <jaustvik@acm.org>
  • Loading branch information
EvilPingu authored and austvik committed Mar 27, 2024
1 parent 9bf4b3f commit dae6eab
Show file tree
Hide file tree
Showing 10 changed files with 186 additions and 12 deletions.
26 changes: 24 additions & 2 deletions bundles/org.openhab.binding.homematic/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down Expand Up @@ -272,6 +276,48 @@ public int getRpcPort(HmChannel channel) {
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.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,12 @@
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;
Expand All @@ -41,6 +43,7 @@
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;
Expand All @@ -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);
Expand Down Expand Up @@ -211,9 +218,9 @@ private synchronized <T> T sendScript(String script, Class<T> clazz) throws IOEx
}

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>");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
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.
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

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;
Expand All @@ -26,11 +28,14 @@
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;
Expand All @@ -43,8 +48,9 @@
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;
}
Expand Down Expand Up @@ -103,11 +109,22 @@ private byte[] send(int port, RpcRequest<String> request) throws IOException {
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");
Expand All @@ -116,7 +133,8 @@ private byte[] send(int port, RpcRequest<String> request) throws IOException {
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
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;
Expand Down Expand Up @@ -132,6 +133,9 @@ private void initializeInternal() {
ex.getMessage(), ex);
disposeInternal();
scheduleReinitialize();
} catch (ConfigurationException ex) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, ex.getMessage());
disposeInternal();
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,23 @@
<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>

Expand Down

0 comments on commit dae6eab

Please sign in to comment.