[remoteopenhab] Connection to the remote server through openHAB Cloud (#10138)

Fix #10055

Signed-off-by: Laurent Garnier <lg.hc@free.fr>
This commit is contained in:
lolodomo
2021-02-14 20:00:14 +01:00
committed by GitHub
parent b9c5a0d158
commit 944682d1f1
6 changed files with 112 additions and 34 deletions

View File

@@ -31,8 +31,11 @@ public class RemoteopenhabServerConfiguration {
public boolean useHttps = false;
public int port = 8080;
public boolean trustedCertificate = false;
public String restPath = "/rest";
public String restPath = "/rest/";
public String token = "";
public String username = "";
public String password = "";
public boolean authenticateAnyway = false;
public int accessibilityInterval = 3;
public int aliveInterval = 5;
public boolean restartIfNoActivity = false;

View File

@@ -150,13 +150,10 @@ public class RemoteopenhabBridgeHandler extends BaseBridgeHandler
}
String urlStr = url.toString();
if (urlStr.endsWith("/")) {
urlStr = urlStr.substring(0, urlStr.length() - 1);
}
logger.debug("REST URL = {}", urlStr);
restClient.setRestUrl(urlStr);
restClient.setAccessToken(config.token);
restClient.setAuthenticationData(config.authenticateAnyway, config.token, config.username, config.password);
if (config.useHttps && config.trustedCertificate) {
restClient.setHttpClient(httpClientTrustingCert);
restClient.setTrustedCertificate(true);

View File

@@ -17,6 +17,7 @@ import java.io.EOFException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -89,7 +90,9 @@ public class RemoteopenhabRestClient {
private @Nullable String restApiVersion;
private Map<String, @Nullable String> apiEndPointsUrls = new HashMap<>();
private @Nullable String topicNamespace;
private boolean authenticateAnyway;
private String accessToken;
private String credentialToken;
private boolean trustedCertificate;
private boolean connected;
private boolean completed;
@@ -104,6 +107,7 @@ public class RemoteopenhabRestClient {
this.eventSourceFactory = eventSourceFactory;
this.jsonParser = jsonParser;
this.accessToken = "";
this.credentialToken = "";
}
public void setHttpClient(HttpClient httpClient) {
@@ -122,8 +126,16 @@ public class RemoteopenhabRestClient {
this.restUrl = restUrl;
}
public void setAccessToken(String accessToken) {
public void setAuthenticationData(boolean authenticateAnyway, String accessToken, String username,
String password) {
this.authenticateAnyway = authenticateAnyway;
this.accessToken = accessToken;
if (username.isBlank() || password.isBlank()) {
this.credentialToken = "";
} else {
String token = username + ":" + password;
this.credentialToken = Base64.getEncoder().encodeToString(token.getBytes(StandardCharsets.UTF_8));
}
}
public void setTrustedCertificate(boolean trustedCertificate) {
@@ -132,7 +144,7 @@ public class RemoteopenhabRestClient {
public void tryApi() throws RemoteopenhabException {
try {
String jsonResponse = executeGetUrl(getRestUrl(), "application/json", false);
String jsonResponse = executeGetUrl(getRestUrl(), "application/json", false, false);
if (jsonResponse.isEmpty()) {
throw new RemoteopenhabException("JSON response is empty");
}
@@ -160,7 +172,7 @@ public class RemoteopenhabRestClient {
url += "&fields=" + fields;
}
boolean asyncReading = fields == null || Arrays.asList(fields.split(",")).contains("state");
String jsonResponse = executeGetUrl(url, "application/json", asyncReading);
String jsonResponse = executeGetUrl(url, "application/json", false, asyncReading);
if (jsonResponse.isEmpty()) {
throw new RemoteopenhabException("JSON response is empty");
}
@@ -174,7 +186,7 @@ public class RemoteopenhabRestClient {
public String getRemoteItemState(String itemName) throws RemoteopenhabException {
try {
String url = String.format("%s/%s/state", getRestApiUrl("items"), itemName);
return executeGetUrl(url, "text/plain", true);
return executeGetUrl(url, "text/plain", false, true);
} catch (RemoteopenhabException e) {
throw new RemoteopenhabException("Failed to get the state of remote item " + itemName
+ " using the items REST API: " + e.getMessage(), e);
@@ -184,7 +196,8 @@ public class RemoteopenhabRestClient {
public void sendCommandToRemoteItem(String itemName, Command command) throws RemoteopenhabException {
try {
String url = String.format("%s/%s", getRestApiUrl("items"), itemName);
executeUrl(HttpMethod.POST, url, "application/json", command.toFullString(), "text/plain", false, true);
executeUrl(HttpMethod.POST, url, "application/json", command.toFullString(), "text/plain", false, false,
true);
} catch (RemoteopenhabException e) {
throw new RemoteopenhabException("Failed to send command to the remote item " + itemName
+ " using the items REST API: " + e.getMessage(), e);
@@ -193,7 +206,7 @@ public class RemoteopenhabRestClient {
public List<RemoteopenhabThing> getRemoteThings() throws RemoteopenhabException {
try {
String jsonResponse = executeGetUrl(getRestApiUrl("things"), "application/json", false);
String jsonResponse = executeGetUrl(getRestApiUrl("things"), "application/json", true, false);
if (jsonResponse.isEmpty()) {
throw new RemoteopenhabException("JSON response is empty");
}
@@ -207,7 +220,7 @@ public class RemoteopenhabRestClient {
public RemoteopenhabThing getRemoteThing(String uid) throws RemoteopenhabException {
try {
String url = String.format("%s/%s", getRestApiUrl("things"), uid);
String jsonResponse = executeGetUrl(url, "application/json", false);
String jsonResponse = executeGetUrl(url, "application/json", true, false);
if (jsonResponse.isEmpty()) {
throw new RemoteopenhabException("JSON response is empty");
}
@@ -224,7 +237,14 @@ public class RemoteopenhabRestClient {
private String getRestApiUrl(String endPoint) throws RemoteopenhabException {
String url = apiEndPointsUrls.get(endPoint);
return url != null ? url : getRestUrl() + "/" + endPoint;
if (url == null) {
url = getRestUrl();
if (!url.endsWith("/")) {
url += "/";
}
url += endPoint;
}
return url;
}
public String getTopicNamespace() {
@@ -250,6 +270,7 @@ public class RemoteopenhabRestClient {
}
private SseEventSource createEventSource(String restSseUrl) {
String credentialToken = restSseUrl.startsWith("https:") || authenticateAnyway ? this.credentialToken : "";
Client client;
// Avoid a timeout exception after 1 minute by setting the read timeout to 0 (infinite)
if (trustedCertificate) {
@@ -259,11 +280,11 @@ public class RemoteopenhabRestClient {
public boolean verify(@Nullable String hostname, @Nullable SSLSession session) {
return true;
}
}).readTimeout(0, TimeUnit.SECONDS).register(new RemoteopenhabStreamingRequestFilter(accessToken))
.build();
}).readTimeout(0, TimeUnit.SECONDS)
.register(new RemoteopenhabStreamingRequestFilter(credentialToken)).build();
} else {
client = clientBuilder.readTimeout(0, TimeUnit.SECONDS)
.register(new RemoteopenhabStreamingRequestFilter(accessToken)).build();
.register(new RemoteopenhabStreamingRequestFilter(credentialToken)).build();
}
SseEventSource eventSource = eventSourceFactory.newSource(client.target(restSseUrl));
eventSource.register(this::onEvent, this::onError, this::onComplete);
@@ -471,18 +492,31 @@ public class RemoteopenhabRestClient {
return parts[2];
}
public String executeGetUrl(String url, String acceptHeader, boolean asyncReading) throws RemoteopenhabException {
return executeUrl(HttpMethod.GET, url, acceptHeader, null, null, asyncReading, true);
public String executeGetUrl(String url, String acceptHeader, boolean provideAccessToken, boolean asyncReading)
throws RemoteopenhabException {
return executeUrl(HttpMethod.GET, url, acceptHeader, null, null, provideAccessToken, asyncReading, true);
}
public String executeUrl(HttpMethod httpMethod, String url, String acceptHeader, @Nullable String content,
@Nullable String contentType, boolean asyncReading, boolean retryIfEOF) throws RemoteopenhabException {
final Request request = httpClient.newRequest(url).method(httpMethod).timeout(REQUEST_TIMEOUT,
TimeUnit.MILLISECONDS);
@Nullable String contentType, boolean provideAccessToken, boolean asyncReading, boolean retryIfEOF)
throws RemoteopenhabException {
final Request request = httpClient.newRequest(url).method(httpMethod)
.timeout(REQUEST_TIMEOUT, TimeUnit.MILLISECONDS).followRedirects(false)
.header(HttpHeaders.ACCEPT, acceptHeader);
request.header(HttpHeaders.ACCEPT, acceptHeader);
if (!accessToken.isEmpty()) {
request.header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken);
if (url.startsWith("https:") || authenticateAnyway) {
boolean useAlternativeHeader = false;
if (!credentialToken.isEmpty()) {
request.header(HttpHeaders.AUTHORIZATION, "Basic " + credentialToken);
useAlternativeHeader = true;
}
if (provideAccessToken && !accessToken.isEmpty()) {
if (useAlternativeHeader) {
request.header("X-OPENHAB-TOKEN", accessToken);
} else {
request.header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken);
}
}
}
if (content != null && (HttpMethod.POST.equals(httpMethod) || HttpMethod.PUT.equals(httpMethod))
@@ -490,6 +524,8 @@ public class RemoteopenhabRestClient {
request.content(new StringContentProvider(content), contentType);
}
logger.debug("Request {} {}", request.getMethod(), request.getURI());
try {
if (asyncReading) {
InputStreamResponseListener listener = new InputStreamResponseListener();
@@ -509,7 +545,17 @@ public class RemoteopenhabRestClient {
} else {
ContentResponse response = request.send();
int statusCode = response.getStatus();
if (statusCode >= HttpStatus.BAD_REQUEST_400) {
if (statusCode == HttpStatus.MOVED_PERMANENTLY_301 || statusCode == HttpStatus.FOUND_302) {
String locationHeader = response.getHeaders().get(HttpHeaders.LOCATION);
if (locationHeader != null && !locationHeader.isBlank()) {
logger.debug("The remopte server redirected the request to this URL: {}", locationHeader);
return executeUrl(httpMethod, locationHeader, acceptHeader, content, contentType,
provideAccessToken, asyncReading, retryIfEOF);
} else {
String statusLine = statusCode + " " + response.getReason();
throw new RemoteopenhabException("HTTP call failed: " + statusLine);
}
} else if (statusCode >= HttpStatus.BAD_REQUEST_400) {
String statusLine = statusCode + " " + response.getReason();
throw new RemoteopenhabException("HTTP call failed: " + statusLine);
}
@@ -525,7 +571,8 @@ public class RemoteopenhabRestClient {
Throwable cause = e.getCause();
if (retryIfEOF && cause instanceof EOFException) {
logger.debug("EOFException - retry the request");
return executeUrl(httpMethod, url, acceptHeader, content, contentType, asyncReading, false);
return executeUrl(httpMethod, url, acceptHeader, content, contentType, provideAccessToken, asyncReading,
false);
} else {
throw new RemoteopenhabException(e);
}

View File

@@ -30,18 +30,18 @@ import org.eclipse.jdt.annotation.Nullable;
@NonNullByDefault
public class RemoteopenhabStreamingRequestFilter implements ClientRequestFilter {
private final String accessToken;
private final String credentialToken;
public RemoteopenhabStreamingRequestFilter(String accessToken) {
this.accessToken = accessToken;
public RemoteopenhabStreamingRequestFilter(String credentialToken) {
this.credentialToken = credentialToken;
}
@Override
public void filter(@Nullable ClientRequestContext requestContext) throws IOException {
if (requestContext != null) {
MultivaluedMap<String, Object> headers = requestContext.getHeaders();
if (!accessToken.isEmpty()) {
headers.putSingle(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken);
if (!credentialToken.isEmpty()) {
headers.putSingle(HttpHeaders.AUTHORIZATION, "Basic " + credentialToken);
}
headers.putSingle(HttpHeaders.CACHE_CONTROL, "no-cache");
}

View File

@@ -43,7 +43,7 @@
<parameter name="restPath" type="text" required="true">
<label>REST API Path</label>
<description>The subpath of the REST API on the remote openHAB server.</description>
<default>/rest</default>
<default>/rest/</default>
<advanced>true</advanced>
</parameter>
@@ -54,6 +54,30 @@
<advanced>true</advanced>
</parameter>
<parameter name="username" type="text">
<label>Username</label>
<description>The username to use when the remote openHAB server is setup to require basic authorization to run its
REST API.</description>
<advanced>true</advanced>
</parameter>
<parameter name="password" type="text">
<context>password</context>
<label>Password</label>
<description>The password to use when the remote openHAB server is setup to require basic authorization to run its
REST API.</description>
<advanced>true</advanced>
</parameter>
<parameter name="authenticateAnyway" type="boolean">
<label>Authenticate Anyway</label>
<description>Set it to true in case you want to pass authentication information even when the communicate with the
remote openHAB server is not secured (only HTTP). This is of course not recommended especially if your connection
is over the Internet. Default is false.</description>
<default>false</default>
<advanced>true</advanced>
</parameter>
<parameter name="accessibilityInterval" type="integer" min="0" step="1" unit="min">
<label>Accessibility Interval</label>
<description>Minutes between checking the remote server accessibility. 0 to disable the check. Default is 3.</description>