From 8981aadafe418814a15986dedb27317fc782abc1 Mon Sep 17 00:00:00 2001 From: Dan Cunningham Date: Sun, 7 Jan 2024 05:39:38 -0800 Subject: [PATCH] [hydrawise] Handle API auth changes (#16221) * Handles a new condition where the service rejects a request as unauthorized, but really we just need to refresh our token after 60 seconds. Signed-off-by: Dan Cunningham Signed-off-by: Ciprian Pascu --- .../api/graphql/HydrawiseGraphQLClient.java | 2 + .../handler/HydrawiseAccountHandler.java | 43 +++++++++++++++---- 2 files changed, 36 insertions(+), 9 deletions(-) diff --git a/bundles/org.openhab.binding.hydrawise/src/main/java/org/openhab/binding/hydrawise/internal/api/graphql/HydrawiseGraphQLClient.java b/bundles/org.openhab.binding.hydrawise/src/main/java/org/openhab/binding/hydrawise/internal/api/graphql/HydrawiseGraphQLClient.java index 7a8ada52f4d..381d78000eb 100644 --- a/bundles/org.openhab.binding.hydrawise/src/main/java/org/openhab/binding/hydrawise/internal/api/graphql/HydrawiseGraphQLClient.java +++ b/bundles/org.openhab.binding.hydrawise/src/main/java/org/openhab/binding/hydrawise/internal/api/graphql/HydrawiseGraphQLClient.java @@ -324,6 +324,8 @@ public class HydrawiseGraphQLClient { } catch (ExecutionException e) { // Hydrawise returns back a 40x status, but without a valid Realm , so jetty throws an exception, // this allows us to catch this in a callback and handle accordingly + logger.debug("ExecutionException", e); + logger.debug("ExecutionException {} {}", responseCode.get(), responseMessage); switch (responseCode.get()) { case 401: case 403: diff --git a/bundles/org.openhab.binding.hydrawise/src/main/java/org/openhab/binding/hydrawise/internal/handler/HydrawiseAccountHandler.java b/bundles/org.openhab.binding.hydrawise/src/main/java/org/openhab/binding/hydrawise/internal/handler/HydrawiseAccountHandler.java index a846c892e24..548efe5d026 100644 --- a/bundles/org.openhab.binding.hydrawise/src/main/java/org/openhab/binding/hydrawise/internal/handler/HydrawiseAccountHandler.java +++ b/bundles/org.openhab.binding.hydrawise/src/main/java/org/openhab/binding/hydrawise/internal/handler/HydrawiseAccountHandler.java @@ -62,6 +62,7 @@ public class HydrawiseAccountHandler extends BaseBridgeHandler implements Access * Minimum amount of time we can poll for updates */ private static final int MIN_REFRESH_SECONDS = 30; + private static final int TOKEN_REFRESH_SECONDS = 60; private static final String BASE_URL = "https://app.hydrawise.com/api/v2/"; private static final String AUTH_URL = BASE_URL + "oauth/access-token"; private static final String CLIENT_SECRET = "zn3CrjglwNV1"; @@ -74,6 +75,7 @@ public class HydrawiseAccountHandler extends BaseBridgeHandler implements Access private @Nullable OAuthClientService oAuthService; private @Nullable HydrawiseGraphQLClient apiClient; private @Nullable ScheduledFuture pollFuture; + private @Nullable ScheduledFuture tokenFuture; private @Nullable Customer lastData; private int refresh; @@ -102,6 +104,7 @@ public class HydrawiseAccountHandler extends BaseBridgeHandler implements Access public void dispose() { logger.debug("Handler disposed."); clearPolling(); + clearTokenRefresh(); OAuthClientService oAuthService = this.oAuthService; if (oAuthService != null) { oAuthService.removeAccessTokenRefreshListener(this); @@ -184,20 +187,40 @@ public class HydrawiseAccountHandler extends BaseBridgeHandler implements Access pollFuture = scheduler.scheduleWithFixedDelay(this::poll, initalDelay, refresh, TimeUnit.SECONDS); } + /** + * The API will randomly reject a request with a 401 not authorized, waiting a min and refreshing the token usually + * fixes it + */ + private synchronized void retryToken() { + clearTokenRefresh(); + tokenFuture = scheduler.schedule(() -> { + try { + OAuthClientService oAuthService = this.oAuthService; + if (oAuthService != null) { + oAuthService.refreshToken(); + initPolling(0, MIN_REFRESH_SECONDS); + } + } catch (OAuthException | IOException | OAuthResponseException e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage()); + } + }, TOKEN_REFRESH_SECONDS, TimeUnit.SECONDS); + } + /** * Stops/clears this thing's polling future */ private void clearPolling() { - ScheduledFuture localFuture = pollFuture; - if (isFutureValid(localFuture)) { - if (localFuture != null) { - localFuture.cancel(false); - } - } + clearFuture(pollFuture); } - private boolean isFutureValid(@Nullable ScheduledFuture future) { - return future != null && !future.isCancelled(); + private void clearTokenRefresh() { + clearFuture(tokenFuture); + } + + private void clearFuture(@Nullable final ScheduledFuture future) { + if (future != null) { + future.cancel(true); + } } private void poll() { @@ -232,8 +255,10 @@ public class HydrawiseAccountHandler extends BaseBridgeHandler implements Access updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); } } catch (HydrawiseAuthenticationException e) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage()); + logger.debug("Token has been rejected, will try to refresh token in {} secs: {}", TOKEN_REFRESH_SECONDS, + e.getLocalizedMessage()); clearPolling(); + retryToken(); } } }