[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:
parent
1b5df97af5
commit
eaae9780ea
@ -18,14 +18,19 @@ It can be extended with different channels.
|
|||||||
| `delay` | no | 0 | Delay between two requests in ms (advanced parameter). |
|
| `delay` | no | 0 | Delay between two requests in ms (advanced parameter). |
|
||||||
| `username` | yes | - | Username for authentication (advanced parameter). |
|
| `username` | yes | - | Username for authentication (advanced parameter). |
|
||||||
| `password` | yes | - | Password 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`. |
|
| `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`. |
|
| `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). |
|
| `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".|
|
| `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.|
|
| `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.
|
*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.
|
||||||
|
|
||||||
|
|||||||
@ -14,10 +14,7 @@ package org.openhab.binding.http.internal;
|
|||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.net.URISyntaxException;
|
import java.net.URISyntaxException;
|
||||||
import java.util.Date;
|
import java.util.*;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.function.Function;
|
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<String, RefreshingUrlCache> urlHandlers = new HashMap<>();
|
||||||
private final Map<ChannelUID, ItemValueConverter> channels = new HashMap<>();
|
private final Map<ChannelUID, ItemValueConverter> channels = new HashMap<>();
|
||||||
private final Map<ChannelUID, String> channelUrls = new HashMap<>();
|
private final Map<ChannelUID, String> channelUrls = new HashMap<>();
|
||||||
private @Nullable Authentication authentication;
|
|
||||||
|
|
||||||
public HttpThingHandler(Thing thing, HttpClientProvider httpClientProvider,
|
public HttpThingHandler(Thing thing, HttpClientProvider httpClientProvider,
|
||||||
ValueTransformationProvider valueTransformationProvider,
|
ValueTransformationProvider valueTransformationProvider,
|
||||||
@ -139,6 +135,7 @@ public class HttpThingHandler extends BaseThingHandler {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check SSL handling and initialize client
|
||||||
if (config.ignoreSSLErrors) {
|
if (config.ignoreSSLErrors) {
|
||||||
logger.info("Using the insecure client for thing '{}'.", thing.getUID());
|
logger.info("Using the insecure client for thing '{}'.", thing.getUID());
|
||||||
httpClient = httpClientProvider.getInsecureClient();
|
httpClient = httpClientProvider.getInsecureClient();
|
||||||
@ -158,29 +155,34 @@ public class HttpThingHandler extends BaseThingHandler {
|
|||||||
channelCount, thing.getUID(), config.delay, config.refresh);
|
channelCount, thing.getUID(), config.delay, config.refresh);
|
||||||
}
|
}
|
||||||
|
|
||||||
authentication = null;
|
// remove empty headers
|
||||||
|
config.headers.removeIf(String::isBlank);
|
||||||
|
|
||||||
|
// configure authentication
|
||||||
if (!config.username.isEmpty()) {
|
if (!config.username.isEmpty()) {
|
||||||
try {
|
try {
|
||||||
|
AuthenticationStore authStore = httpClient.getAuthenticationStore();
|
||||||
URI uri = new URI(config.baseURL);
|
URI uri = new URI(config.baseURL);
|
||||||
switch (config.authMode) {
|
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:
|
case BASIC:
|
||||||
authentication = new BasicAuthentication(uri, Authentication.ANY_REALM, config.username,
|
authStore.addAuthentication(new BasicAuthentication(uri, Authentication.ANY_REALM,
|
||||||
config.password);
|
config.username, config.password));
|
||||||
logger.debug("Basic Authentication configured for thing '{}'", thing.getUID());
|
logger.debug("Basic Authentication configured for thing '{}'", thing.getUID());
|
||||||
break;
|
break;
|
||||||
case DIGEST:
|
case DIGEST:
|
||||||
authentication = new DigestAuthentication(uri, Authentication.ANY_REALM, config.username,
|
authStore.addAuthentication(new DigestAuthentication(uri, Authentication.ANY_REALM,
|
||||||
config.password);
|
config.username, config.password));
|
||||||
logger.debug("Digest Authentication configured for thing '{}'", thing.getUID());
|
logger.debug("Digest Authentication configured for thing '{}'", thing.getUID());
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
logger.warn("Unknown authentication method '{}' for thing '{}'", config.authMode,
|
logger.warn("Unknown authentication method '{}' for thing '{}'", config.authMode,
|
||||||
thing.getUID());
|
thing.getUID());
|
||||||
}
|
}
|
||||||
if (authentication != null) {
|
|
||||||
AuthenticationStore authStore = httpClient.getAuthenticationStore();
|
|
||||||
authStore.addAuthentication(authentication);
|
|
||||||
}
|
|
||||||
} catch (URISyntaxException e) {
|
} catch (URISyntaxException e) {
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||||
"failed to create authentication: baseUrl is invalid");
|
"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());
|
logger.debug("No authentication configured for thing '{}'", thing.getUID());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// create channels
|
||||||
thing.getChannels().forEach(this::createChannel);
|
thing.getChannels().forEach(this::createChannel);
|
||||||
|
|
||||||
updateStatus(ThingStatus.ONLINE);
|
updateStatus(ThingStatus.ONLINE);
|
||||||
|
|||||||
@ -21,6 +21,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
|||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public enum HttpAuthMode {
|
public enum HttpAuthMode {
|
||||||
|
BASIC_PREEMPTIVE,
|
||||||
BASIC,
|
BASIC,
|
||||||
DIGEST
|
DIGEST
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,8 +12,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.openhab.binding.http.internal.config;
|
package org.openhab.binding.http.internal.config;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
@ -43,5 +42,6 @@ public class HttpThingConfig {
|
|||||||
|
|
||||||
public boolean ignoreSSLErrors = false;
|
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<>();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -52,6 +52,7 @@
|
|||||||
<label>Authentication Mode</label>
|
<label>Authentication Mode</label>
|
||||||
<options>
|
<options>
|
||||||
<option value="BASIC">Basic Authentication</option>
|
<option value="BASIC">Basic Authentication</option>
|
||||||
|
<option value="BASIC_PREEMPTIVE">Preemptive Basic Authentication</option>
|
||||||
<option value="DIGEST">Digest Authentication</option>
|
<option value="DIGEST">Digest Authentication</option>
|
||||||
</options>
|
</options>
|
||||||
<default>BASIC</default>
|
<default>BASIC</default>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user