[http] add pre-emptive basic authentication and fix header handling (#9584)

* 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>
This commit is contained in:
J-N-K 2020-12-31 12:23:32 +01:00 committed by GitHub
parent 1b5df97af5
commit eaae9780ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 29 additions and 19 deletions

View 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.

View 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);

View File

@ -21,6 +21,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
*/
@NonNullByDefault
public enum HttpAuthMode {
BASIC_PREEMPTIVE,
BASIC,
DIGEST
}

View 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<>();
}

View 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>