]> git.basschouten.com Git - openhab-addons.git/commitdiff
[http] add pre-emptive basic authentication and fix header handling (#9584)
authorJ-N-K <J-N-K@users.noreply.github.com>
Thu, 31 Dec 2020 11:23:32 +0000 (12:23 +0100)
committerGitHub <noreply@github.com>
Thu, 31 Dec 2020 11:23:32 +0000 (12:23 +0100)
* add preemptive basic authentication

Signed-off-by: Jan N. Klug <jan.n.klug@rub.de>
* improve header handling

Signed-off-by: Jan N. Klug <jan.n.klug@rub.de>
* Update bundles/org.openhab.binding.http/README.md

Co-authored-by: t2000 <t2000@users.noreply.github.com>
Co-authored-by: t2000 <t2000@users.noreply.github.com>
bundles/org.openhab.binding.http/README.md
bundles/org.openhab.binding.http/src/main/java/org/openhab/binding/http/internal/HttpThingHandler.java
bundles/org.openhab.binding.http/src/main/java/org/openhab/binding/http/internal/config/HttpAuthMode.java
bundles/org.openhab.binding.http/src/main/java/org/openhab/binding/http/internal/config/HttpThingConfig.java
bundles/org.openhab.binding.http/src/main/resources/OH-INF/thing/thing-types.xml

index a8c540f699c9b894099ce4838f5059efa93538a3..333c4041e46504b9cb6c1a3b263de543e5d99ce5 100644 (file)
@@ -18,14 +18,19 @@ It can be extended with different channels.
 | `delay`           | no       |    0    | Delay between two requests in ms (advanced parameter). |
 | `username`        | yes      |    -    | Username for authentication (advanced parameter). |
 | `password`        | yes      |    -    | Password for authentication (advanced parameter). |
-| `authMode`        | no       |  BASIC  | Authentication mode, `BASIC` or `DIGEST` (advanced parameter). |
+| `authMode`        | no       |  BASIC  | Authentication mode, `BASIC`, `BASIC_PREEMPTIVE` or `DIGEST` (advanced parameter). |
 | `commandMethod`   | no       |   GET   | Method used for sending commands `GET`, `PUT`, `POST`. |
 | `contentType`     | yes      |    -    | MIME content-type of the command requests. Only used for  `PUT` and `POST`. |
 | `encoding`        | yes      |    -    | Encoding to be used if no encoding is found in responses (advanced parameter). |  
 | `headers`         | yes      |    -    | Additional headers that are sent along with the request. Format is "header=value".| 
 | `ignoreSSLErrors` | no       |  false  | If set to true ignores invalid SSL certificate errors. This is potentially dangerous.|
 
-*Note:* optional "no" means that you have to configure a value unless a default is provided and you are ok with that setting.
+*Note:* Optional "no" means that you have to configure a value unless a default is provided and you are ok with that setting.
+
+*Note:* The `BASIC_PREEMPTIVE` mode adds basic authentication headers even if the server did not request authentication.
+This is dangerous and might be misused.
+The option exists to be able to authenticate when the server is not sending the proper 401/Unauthorized code.
+Authentication might fail if redirections are involved as headers are stripper prior to redirection.
 
 *Note:* If you rate-limit requests by using the `delay` parameter you have to make sure that the time between two refreshes is larger than the time needed for one refresh cycle.
 
index 343145a92e4779613bfcfe043530903dfcb57802..f7bce25725997636f37b58888b0137715a8029dd 100644 (file)
@@ -14,10 +14,7 @@ package org.openhab.binding.http.internal;
 
 import java.net.URI;
 import java.net.URISyntaxException;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
 import java.util.function.Function;
@@ -85,7 +82,6 @@ public class HttpThingHandler extends BaseThingHandler {
     private final Map<String, RefreshingUrlCache> urlHandlers = new HashMap<>();
     private final Map<ChannelUID, ItemValueConverter> channels = new HashMap<>();
     private final Map<ChannelUID, String> channelUrls = new HashMap<>();
-    private @Nullable Authentication authentication;
 
     public HttpThingHandler(Thing thing, HttpClientProvider httpClientProvider,
             ValueTransformationProvider valueTransformationProvider,
@@ -139,6 +135,7 @@ public class HttpThingHandler extends BaseThingHandler {
             return;
         }
 
+        // check SSL handling and initialize client
         if (config.ignoreSSLErrors) {
             logger.info("Using the insecure client for thing '{}'.", thing.getUID());
             httpClient = httpClientProvider.getInsecureClient();
@@ -158,29 +155,34 @@ public class HttpThingHandler extends BaseThingHandler {
                     channelCount, thing.getUID(), config.delay, config.refresh);
         }
 
-        authentication = null;
+        // remove empty headers
+        config.headers.removeIf(String::isBlank);
+
+        // configure authentication
         if (!config.username.isEmpty()) {
             try {
+                AuthenticationStore authStore = httpClient.getAuthenticationStore();
                 URI uri = new URI(config.baseURL);
                 switch (config.authMode) {
+                    case BASIC_PREEMPTIVE:
+                        config.headers.add("Authorization=Basic " + Base64.getEncoder()
+                                .encodeToString((config.username + ":" + config.password).getBytes()));
+                        logger.debug("Preemptive Basic Authentication configured for thing '{}'", thing.getUID());
+                        break;
                     case BASIC:
-                        authentication = new BasicAuthentication(uri, Authentication.ANY_REALM, config.username,
-                                config.password);
+                        authStore.addAuthentication(new BasicAuthentication(uri, Authentication.ANY_REALM,
+                                config.username, config.password));
                         logger.debug("Basic Authentication configured for thing '{}'", thing.getUID());
                         break;
                     case DIGEST:
-                        authentication = new DigestAuthentication(uri, Authentication.ANY_REALM, config.username,
-                                config.password);
+                        authStore.addAuthentication(new DigestAuthentication(uri, Authentication.ANY_REALM,
+                                config.username, config.password));
                         logger.debug("Digest Authentication configured for thing '{}'", thing.getUID());
                         break;
                     default:
                         logger.warn("Unknown authentication method '{}' for thing '{}'", config.authMode,
                                 thing.getUID());
                 }
-                if (authentication != null) {
-                    AuthenticationStore authStore = httpClient.getAuthenticationStore();
-                    authStore.addAuthentication(authentication);
-                }
             } catch (URISyntaxException e) {
                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
                         "failed to create authentication: baseUrl is invalid");
@@ -189,6 +191,7 @@ public class HttpThingHandler extends BaseThingHandler {
             logger.debug("No authentication configured for thing '{}'", thing.getUID());
         }
 
+        // create channels
         thing.getChannels().forEach(this::createChannel);
 
         updateStatus(ThingStatus.ONLINE);
index c78c21ff57deea666d5ea21ccc867daf0d34fee3..29aa65f349005193afe06b113aebe2fe9bb0743f 100644 (file)
@@ -21,6 +21,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
  */
 @NonNullByDefault
 public enum HttpAuthMode {
+    BASIC_PREEMPTIVE,
     BASIC,
     DIGEST
 }
index 258f94466a97f0d0eb2bcb4193d3c384db44a112..d6df243164373c5e81c5021cb3d9edfa7ed7427a 100644 (file)
@@ -12,8 +12,7 @@
  */
 package org.openhab.binding.http.internal.config;
 
-import java.util.Collections;
-import java.util.List;
+import java.util.ArrayList;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
@@ -43,5 +42,6 @@ public class HttpThingConfig {
 
     public boolean ignoreSSLErrors = false;
 
-    public List<String> headers = Collections.emptyList();
+    // ArrayList is required as implementation because list may be modified later
+    public ArrayList<String> headers = new ArrayList<>();
 }
index f298a53c5df81eb8de9b2d6e4b7ce20391ebbc4b..61e14edb83b095cf1ddbe926750ac16cd81e7f0c 100644 (file)
@@ -52,6 +52,7 @@
                                <label>Authentication Mode</label>
                                <options>
                                        <option value="BASIC">Basic Authentication</option>
+                                       <option value="BASIC_PREEMPTIVE">Preemptive Basic Authentication</option>
                                        <option value="DIGEST">Digest Authentication</option>
                                </options>
                                <default>BASIC</default>