[fronius] Fix invalid credentials lead to unexpected exception (#18130)
With the changes from this PR, the status code is now properly read and for 401, a meaningful warnings is logged instead. Signed-off-by: Florian Hotze <dev@florianhotze.com>pull/18131/head
parent
5fc40cc353
commit
8f77e16e18
|
@ -77,8 +77,9 @@ public class FroniusBatteryControl {
|
|||
*
|
||||
* @return the time of use settings
|
||||
* @throws FroniusCommunicationException if an error occurs during communication with the inverter
|
||||
* @throws FroniusUnauthorizedException when the login failed due to invalid credentials
|
||||
*/
|
||||
private TimeOfUseRecords getTimeOfUse() throws FroniusCommunicationException {
|
||||
private TimeOfUseRecords getTimeOfUse() throws FroniusCommunicationException, FroniusUnauthorizedException {
|
||||
// Login and get the auth header for the next request
|
||||
String authHeader = FroniusConfigAuthUtil.login(httpClient, baseUri, username, password, HttpMethod.GET,
|
||||
timeOfUseUri.getPath(), API_TIMEOUT);
|
||||
|
@ -107,8 +108,10 @@ public class FroniusBatteryControl {
|
|||
*
|
||||
* @param records the time of use settings
|
||||
* @throws FroniusCommunicationException if an error occurs during communication with the inverter
|
||||
* @throws FroniusUnauthorizedException when the login failed due to invalid credentials
|
||||
*/
|
||||
private void setTimeOfUse(TimeOfUseRecords records) throws FroniusCommunicationException {
|
||||
private void setTimeOfUse(TimeOfUseRecords records)
|
||||
throws FroniusCommunicationException, FroniusUnauthorizedException {
|
||||
// Login and get the auth header for the next request
|
||||
String authHeader = FroniusConfigAuthUtil.login(httpClient, baseUri, username, password, HttpMethod.POST,
|
||||
timeOfUseUri.getPath(), API_TIMEOUT);
|
||||
|
@ -127,8 +130,9 @@ public class FroniusBatteryControl {
|
|||
* inverter.
|
||||
*
|
||||
* @throws FroniusCommunicationException when an error occurs during communication with the inverter
|
||||
* @throws FroniusUnauthorizedException when the login failed due to invalid credentials
|
||||
*/
|
||||
public void reset() throws FroniusCommunicationException {
|
||||
public void reset() throws FroniusCommunicationException, FroniusUnauthorizedException {
|
||||
setTimeOfUse(new TimeOfUseRecords(new TimeOfUseRecord[0]));
|
||||
}
|
||||
|
||||
|
@ -136,8 +140,9 @@ public class FroniusBatteryControl {
|
|||
* Holds the battery charge right now, i.e. prevents the battery from discharging.
|
||||
*
|
||||
* @throws FroniusCommunicationException when an error occurs during communication with the inverter
|
||||
* @throws FroniusUnauthorizedException when the login failed due to invalid credentials
|
||||
*/
|
||||
public void holdBatteryCharge() throws FroniusCommunicationException {
|
||||
public void holdBatteryCharge() throws FroniusCommunicationException, FroniusUnauthorizedException {
|
||||
reset();
|
||||
addHoldBatteryChargeSchedule(BEGIN_OF_DAY, END_OF_DAY);
|
||||
}
|
||||
|
@ -149,8 +154,10 @@ public class FroniusBatteryControl {
|
|||
* @param from start time of the hold charge period
|
||||
* @param until end time of the hold charge period
|
||||
* @throws FroniusCommunicationException when an error occurs during communication with the inverter
|
||||
* @throws FroniusUnauthorizedException when the login failed due to invalid credentials
|
||||
*/
|
||||
public void addHoldBatteryChargeSchedule(LocalTime from, LocalTime until) throws FroniusCommunicationException {
|
||||
public void addHoldBatteryChargeSchedule(LocalTime from, LocalTime until)
|
||||
throws FroniusCommunicationException, FroniusUnauthorizedException {
|
||||
TimeOfUseRecord[] currentTimeOfUse = getTimeOfUse().records();
|
||||
TimeOfUseRecord[] timeOfUse = new TimeOfUseRecord[currentTimeOfUse.length + 1];
|
||||
System.arraycopy(currentTimeOfUse, 0, timeOfUse, 0, currentTimeOfUse.length);
|
||||
|
@ -166,8 +173,10 @@ public class FroniusBatteryControl {
|
|||
*
|
||||
* @param power the power to charge the battery with
|
||||
* @throws FroniusCommunicationException when an error occurs during communication with the inverter
|
||||
* @throws FroniusUnauthorizedException when the login failed due to invalid credentials
|
||||
*/
|
||||
public void forceBatteryCharging(QuantityType<Power> power) throws FroniusCommunicationException {
|
||||
public void forceBatteryCharging(QuantityType<Power> power)
|
||||
throws FroniusCommunicationException, FroniusUnauthorizedException {
|
||||
reset();
|
||||
addForcedBatteryChargingSchedule(BEGIN_OF_DAY, END_OF_DAY, power);
|
||||
}
|
||||
|
@ -179,9 +188,10 @@ public class FroniusBatteryControl {
|
|||
* @param until end time of the forced charge period
|
||||
* @param power the power to charge the battery with
|
||||
* @throws FroniusCommunicationException when an error occurs during communication with the inverter
|
||||
* @throws FroniusUnauthorizedException when the login failed due to invalid credentials
|
||||
*/
|
||||
public void addForcedBatteryChargingSchedule(LocalTime from, LocalTime until, QuantityType<Power> power)
|
||||
throws FroniusCommunicationException {
|
||||
throws FroniusCommunicationException, FroniusUnauthorizedException {
|
||||
TimeOfUseRecords currentTimeOfUse = getTimeOfUse();
|
||||
TimeOfUseRecord[] timeOfUse = new TimeOfUseRecord[currentTimeOfUse.records().length + 1];
|
||||
System.arraycopy(currentTimeOfUse.records(), 0, timeOfUse, 0, currentTimeOfUse.records().length);
|
||||
|
|
|
@ -19,14 +19,11 @@ import java.security.NoSuchAlgorithmException;
|
|||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.eclipse.jetty.client.HttpClient;
|
||||
import org.eclipse.jetty.client.api.ContentResponse;
|
||||
import org.eclipse.jetty.client.api.Request;
|
||||
import org.eclipse.jetty.client.api.Response;
|
||||
import org.eclipse.jetty.http.HttpHeader;
|
||||
|
@ -69,10 +66,10 @@ public class FroniusConfigAuthUtil {
|
|||
throws IOException {
|
||||
LOGGER.debug("Sending login request to get authentication challenge");
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
Request initialRequest = httpClient.newRequest(loginUri).timeout(timeout, TimeUnit.MILLISECONDS);
|
||||
XWwwAuthenticateHeaderListener XWwwAuthenticateHeaderListener = new XWwwAuthenticateHeaderListener(latch);
|
||||
initialRequest.onResponseHeaders(XWwwAuthenticateHeaderListener);
|
||||
initialRequest.send(result -> latch.countDown());
|
||||
Request request = httpClient.newRequest(loginUri).timeout(timeout, TimeUnit.MILLISECONDS);
|
||||
XWwwAuthenticateHeaderListener xWwwAuthenticateHeaderListener = new XWwwAuthenticateHeaderListener(latch);
|
||||
request.onResponseHeaders(xWwwAuthenticateHeaderListener);
|
||||
request.send(result -> latch.countDown());
|
||||
// Wait for the request to complete
|
||||
try {
|
||||
latch.await();
|
||||
|
@ -80,7 +77,7 @@ public class FroniusConfigAuthUtil {
|
|||
throw new RuntimeException(ie);
|
||||
}
|
||||
|
||||
String authHeader = XWwwAuthenticateHeaderListener.getAuthHeader();
|
||||
String authHeader = xWwwAuthenticateHeaderListener.getAuthHeader();
|
||||
if (authHeader == null) {
|
||||
throw new IOException("No authentication header found in login response");
|
||||
}
|
||||
|
@ -161,21 +158,40 @@ public class FroniusConfigAuthUtil {
|
|||
* @param authHeader the authentication header to use for the login request
|
||||
* @throws InterruptedException when the request is interrupted
|
||||
* @throws FroniusCommunicationException when the login request failed
|
||||
* @throws FroniusUnauthorizedException when the login failed due to invalid credentials
|
||||
*/
|
||||
private static void performLoginRequest(HttpClient httpClient, URI loginUri, String authHeader, int timeout)
|
||||
throws InterruptedException, FroniusCommunicationException {
|
||||
Request loginRequest = httpClient.newRequest(loginUri).header(HttpHeader.AUTHORIZATION, authHeader)
|
||||
.timeout(timeout, TimeUnit.MILLISECONDS);
|
||||
ContentResponse loginResponse;
|
||||
throws InterruptedException, FroniusCommunicationException, FroniusUnauthorizedException {
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
Request request = httpClient.newRequest(loginUri).header(HttpHeader.AUTHORIZATION, authHeader).timeout(timeout,
|
||||
TimeUnit.MILLISECONDS);
|
||||
StatusListener statusListener = new StatusListener(latch);
|
||||
request.onResponseBegin(statusListener);
|
||||
Integer status;
|
||||
try {
|
||||
loginResponse = loginRequest.send();
|
||||
if (loginResponse.getStatus() != 200) {
|
||||
throw new FroniusCommunicationException(
|
||||
"Failed to send login request, status code: " + loginResponse.getStatus());
|
||||
request.send(result -> latch.countDown());
|
||||
// Wait for the request to complete
|
||||
try {
|
||||
latch.await();
|
||||
} catch (InterruptedException ie) {
|
||||
throw new RuntimeException(ie);
|
||||
}
|
||||
} catch (TimeoutException | ExecutionException e) {
|
||||
|
||||
status = statusListener.getStatus();
|
||||
if (status == null) {
|
||||
throw new FroniusCommunicationException("Failed to send login request: No status code received.");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new FroniusCommunicationException("Failed to send login request", e);
|
||||
}
|
||||
|
||||
if (status == 401) {
|
||||
throw new FroniusUnauthorizedException(
|
||||
"Failed to send login request, status code: 401 Unauthorized. Please check your credentials.");
|
||||
}
|
||||
if (status != 200) {
|
||||
throw new FroniusCommunicationException("Failed to send login request, status code: " + status);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -191,9 +207,11 @@ public class FroniusConfigAuthUtil {
|
|||
* @param timeout the timeout in milliseconds for the login requests
|
||||
* @return the authentication header for the next request
|
||||
* @throws FroniusCommunicationException when the login failed or interrupted
|
||||
* @throws FroniusUnauthorizedException when the login failed due to invalid credentials
|
||||
*/
|
||||
public static synchronized String login(HttpClient httpClient, URI baseUri, String username, String password,
|
||||
HttpMethod method, String relativeUrl, int timeout) throws FroniusCommunicationException {
|
||||
HttpMethod method, String relativeUrl, int timeout)
|
||||
throws FroniusCommunicationException, FroniusUnauthorizedException {
|
||||
// Perform request to get authentication parameters
|
||||
LOGGER.debug("Getting authentication parameters");
|
||||
URI loginUri = baseUri.resolve(URI.create(LOGIN_ENDPOINT + "?user=" + username));
|
||||
|
@ -246,6 +264,8 @@ public class FroniusConfigAuthUtil {
|
|||
Thread.sleep(500 * attemptCount);
|
||||
attemptCount++;
|
||||
lastException = e;
|
||||
} catch (FroniusUnauthorizedException e) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
if (attemptCount >= 3) {
|
||||
|
@ -269,6 +289,9 @@ public class FroniusConfigAuthUtil {
|
|||
|
||||
/**
|
||||
* Listener to extract the X-Www-Authenticate header from the response of a {@link Request}.
|
||||
* Required to mitigate {@link org.eclipse.jetty.client.HttpResponseException}: HTTP protocol violation:
|
||||
* Authentication challenge without WWW-Authenticate header being thrown due to Fronius non-standard authentication
|
||||
* header.
|
||||
*/
|
||||
private static class XWwwAuthenticateHeaderListener extends Response.Listener.Adapter {
|
||||
private final CountDownLatch latch;
|
||||
|
@ -288,4 +311,30 @@ public class FroniusConfigAuthUtil {
|
|||
return authHeader;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener to extract the HTTP status code from the response of a {@link Request} on response begin.
|
||||
* Required to mitigate {@link org.eclipse.jetty.client.HttpResponseException}: HTTP protocol violation:
|
||||
* Authentication challenge without WWW-Authenticate header being thrown due to Fronius non-standard authentication
|
||||
* header.
|
||||
*/
|
||||
private static class StatusListener extends Response.Listener.Adapter {
|
||||
private final CountDownLatch latch;
|
||||
private @Nullable Integer status;
|
||||
|
||||
public StatusListener(CountDownLatch latch) {
|
||||
this.latch = latch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBegin(Response response) {
|
||||
this.status = response.getStatus();
|
||||
latch.countDown();
|
||||
super.onBegin(response);
|
||||
}
|
||||
|
||||
public @Nullable Integer getStatus() {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* Copyright (c) 2010-2025 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.fronius.internal.api;
|
||||
|
||||
/**
|
||||
* Exception for 401 response from the Fronius controller.
|
||||
*
|
||||
* @author Florian Hotze - Initial contribution
|
||||
*/
|
||||
public class FroniusUnauthorizedException extends Exception {
|
||||
public FroniusUnauthorizedException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
|
@ -29,6 +29,7 @@ import org.openhab.binding.fronius.internal.FroniusBridgeConfiguration;
|
|||
import org.openhab.binding.fronius.internal.action.FroniusSymoInverterActions;
|
||||
import org.openhab.binding.fronius.internal.api.FroniusBatteryControl;
|
||||
import org.openhab.binding.fronius.internal.api.FroniusCommunicationException;
|
||||
import org.openhab.binding.fronius.internal.api.FroniusUnauthorizedException;
|
||||
import org.openhab.binding.fronius.internal.api.dto.ValueUnit;
|
||||
import org.openhab.binding.fronius.internal.api.dto.inverter.InverterDeviceStatus;
|
||||
import org.openhab.binding.fronius.internal.api.dto.inverter.InverterRealtimeBody;
|
||||
|
@ -115,6 +116,8 @@ public class FroniusSymoInverterHandler extends FroniusBaseThingHandler {
|
|||
return true;
|
||||
} catch (FroniusCommunicationException e) {
|
||||
logger.warn("Failed to reset battery control", e);
|
||||
} catch (FroniusUnauthorizedException e) {
|
||||
logger.warn("Failed to reset battery control: Invalid username or password");
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
@ -128,6 +131,8 @@ public class FroniusSymoInverterHandler extends FroniusBaseThingHandler {
|
|||
return true;
|
||||
} catch (FroniusCommunicationException e) {
|
||||
logger.warn("Failed to set battery control to hold battery charge", e);
|
||||
} catch (FroniusUnauthorizedException e) {
|
||||
logger.warn("Failed to set battery control to hold battery charge: Invalid username or password");
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
@ -141,6 +146,9 @@ public class FroniusSymoInverterHandler extends FroniusBaseThingHandler {
|
|||
return true;
|
||||
} catch (FroniusCommunicationException e) {
|
||||
logger.warn("Failed to add hold battery charge schedule to battery control", e);
|
||||
} catch (FroniusUnauthorizedException e) {
|
||||
logger.warn(
|
||||
"Failed to add hold battery charge schedule to battery control: Invalid username or password");
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
@ -154,6 +162,8 @@ public class FroniusSymoInverterHandler extends FroniusBaseThingHandler {
|
|||
return true;
|
||||
} catch (FroniusCommunicationException e) {
|
||||
logger.warn("Failed to set battery control to force battery charge", e);
|
||||
} catch (FroniusUnauthorizedException e) {
|
||||
logger.warn("Failed to set battery control to force battery charge: Invalid username or password");
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
@ -167,6 +177,9 @@ public class FroniusSymoInverterHandler extends FroniusBaseThingHandler {
|
|||
return true;
|
||||
} catch (FroniusCommunicationException e) {
|
||||
logger.warn("Failed to add forced battery charge schedule to battery control", e);
|
||||
} catch (FroniusUnauthorizedException e) {
|
||||
logger.warn(
|
||||
"Failed to add forced battery charge schedule to battery control: Invalid username or password");
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
|
Loading…
Reference in New Issue