diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml index 85e2220f5551..89b1ccaa0a9c 100644 --- a/bom/openhab-addons/pom.xml +++ b/bom/openhab-addons/pom.xml @@ -751,13 +751,11 @@ org.openhab.binding.lifx ${project.version} - + org.openhab.addons.bundles org.openhab.binding.linuxinput diff --git a/bundles/org.openhab.binding.linky/src/main/feature/feature.xml.bak b/bundles/org.openhab.binding.linky/src/main/feature/feature.xml similarity index 100% rename from bundles/org.openhab.binding.linky/src/main/feature/feature.xml.bak rename to bundles/org.openhab.binding.linky/src/main/feature/feature.xml diff --git a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/LinkyConfiguration.java b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/LinkyConfiguration.java index 5c5029c58f82..f14e2b378a3a 100644 --- a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/LinkyConfiguration.java +++ b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/LinkyConfiguration.java @@ -12,15 +12,22 @@ */ package org.openhab.binding.linky.internal; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * The {@link LinkyConfiguration} is the class used to match the * thing configuration. * * @author Gaƫl L'hopital - Initial contribution */ +@NonNullByDefault public class LinkyConfiguration { public static final String INTERNAL_AUTH_ID = "internalAuthId"; - public String username; - public String password; - public String internalAuthId; + public String username = ""; + public String password = ""; + public String internalAuthId = ""; + + public boolean seemsValid() { + return !username.isBlank() && !password.isBlank() && !internalAuthId.isBlank(); + } } diff --git a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/LinkyHandlerFactory.java b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/LinkyHandlerFactory.java index d0edaa6a9259..1f2e28bdd5eb 100644 --- a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/LinkyHandlerFactory.java +++ b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/LinkyHandlerFactory.java @@ -47,9 +47,10 @@ @NonNullByDefault @Component(service = ThingHandlerFactory.class, configurationPid = "binding.linky") public class LinkyHandlerFactory extends BaseThingHandlerFactory { + private static final DateTimeFormatter LINKY_FORMATTER = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSX"); + private final Logger logger = LoggerFactory.getLogger(LinkyHandlerFactory.class); - private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss.SSSX"); private final LocaleProvider localeProvider; private final Gson gson; private final HttpClient httpClient; @@ -60,7 +61,7 @@ public LinkyHandlerFactory(final @Reference LocaleProvider localeProvider, this.localeProvider = localeProvider; this.gson = new GsonBuilder().registerTypeAdapter(ZonedDateTime.class, (JsonDeserializer) (json, type, jsonDeserializationContext) -> ZonedDateTime - .parse(json.getAsJsonPrimitive().getAsString(), formatter)) + .parse(json.getAsJsonPrimitive().getAsString(), LINKY_FORMATTER)) .create(); this.httpClient = httpClientFactory.createHttpClient(LinkyBindingConstants.BINDING_ID); } diff --git a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/api/EnedisHttpApi.java b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/api/EnedisHttpApi.java index fb4fad25cb1b..47f964df666a 100644 --- a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/api/EnedisHttpApi.java +++ b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/api/EnedisHttpApi.java @@ -64,18 +64,19 @@ public class EnedisHttpApi { private final Logger logger = LoggerFactory.getLogger(EnedisHttpApi.class); private final Gson gson; private final HttpClient httpClient; - private final LinkyConfiguration config; private boolean connected = false; + private final CookieStore cookieStore; + private final LinkyConfiguration config; public EnedisHttpApi(LinkyConfiguration config, Gson gson, HttpClient httpClient) { this.gson = gson; this.httpClient = httpClient; this.config = config; + this.cookieStore = httpClient.getCookieStore(); + addCookie(LinkyConfiguration.INTERNAL_AUTH_ID, config.internalAuthId); } public void initialize() throws LinkyException { - addCookie(LinkyConfiguration.INTERNAL_AUTH_ID, config.internalAuthId); - logger.debug("Starting login process for user : {}", config.username); try { @@ -109,31 +110,39 @@ public void initialize() throws LinkyException { logger.debug( "Step 3 : auth1 - retrieve the template, thanks to cookie internalAuthId, user is already set"); - result = httpClient.POST(url).send(); + result = httpClient.POST(url).header("X-NoSession", "true").header("X-Password", "anonymous") + .header("X-Requested-With", "XMLHttpRequest").header("X-Username", "anonymous").send(); if (result.getStatus() != 200) { throw new LinkyException("Connection failed step 3 - auth1 : " + result.getContentAsString()); } AuthData authData = gson.fromJson(result.getContentAsString(), AuthData.class); - if (authData.callbacks.size() < 2 || authData.callbacks.get(0).input.size() == 0 - || authData.callbacks.get(1).input.size() == 0 || !config.username + if (authData == null || authData.callbacks.size() < 2 || authData.callbacks.get(0).input.isEmpty() + || authData.callbacks.get(1).input.isEmpty() || !config.username .equals(Objects.requireNonNull(authData.callbacks.get(0).input.get(0)).valueAsString())) { throw new LinkyException("Authentication error, the authentication_cookie is probably wrong"); } authData.callbacks.get(1).input.get(0).value = config.password; - url = "https://mon-compte.enedis.fr/auth/json/authenticate?realm=/enedis&spEntityID=SP-ODW-PROD&goto=/auth/SSOPOST/metaAlias/enedis/providerIDP?ReqID%" + url = URL_MON_COMPTE + + "/auth/json/authenticate?realm=/enedis&spEntityID=SP-ODW-PROD&goto=/auth/SSOPOST/metaAlias/enedis/providerIDP?ReqID%" + reqId + "%26index%3Dnull%26acsURL%3Dhttps://apps.lincs.enedis.fr/saml/SSO%26spEntityID%3DSP-ODW-PROD%26binding%3Durn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST&AMAuthCookie="; logger.debug("Step 3 : auth2 - send the auth data"); result = httpClient.POST(url).header(HttpHeader.CONTENT_TYPE, "application/json") + .header("X-NoSession", "true").header("X-Password", "anonymous") + .header("X-Requested-With", "XMLHttpRequest").header("X-Username", "anonymous") .content(new StringContentProvider(gson.toJson(authData))).send(); if (result.getStatus() != 200) { throw new LinkyException("Connection failed step 3 - auth2 : " + result.getContentAsString()); } AuthResult authResult = gson.fromJson(result.getContentAsString(), AuthResult.class); + if (authResult == null) { + throw new LinkyException("Invalid authentication result data"); + } + logger.debug("Add the tokenId cookie"); addCookie("enedisExt", authResult.tokenId); @@ -155,18 +164,17 @@ public void initialize() throws LinkyException { } } - public String getLocation(ContentResponse response) { + private String getLocation(ContentResponse response) { return response.getHeaders().get(HttpHeader.LOCATION); } - public void disconnect() throws LinkyException { + private void disconnect() throws LinkyException { if (connected) { logger.debug("Logout process"); try { // Three times in a row to get disconnected String location = getLocation(httpClient.GET(URL_APPS_LINCS + "/logout")); location = getLocation(httpClient.GET(location)); location = getLocation(httpClient.GET(location)); - CookieStore cookieStore = httpClient.getCookieStore(); cookieStore.removeAll(); connected = false; } catch (InterruptedException | ExecutionException | TimeoutException e) { @@ -184,7 +192,6 @@ public void dispose() throws LinkyException { } private void addCookie(String key, String value) { - CookieStore cookieStore = httpClient.getCookieStore(); HttpCookie cookie = new HttpCookie(key, value); cookie.setDomain(".enedis.fr"); cookie.setPath("/"); @@ -220,6 +227,9 @@ public PrmInfo getPrmInfo() throws LinkyException { } try { PrmInfo[] prms = gson.fromJson(data, PrmInfo[].class); + if (prms == null || prms.length < 1) { + throw new LinkyException("Invalid prms data received"); + } return prms[0]; } catch (JsonSyntaxException e) { logger.debug("invalid JSON response not matching PrmInfo[].class: {}", data); @@ -259,6 +269,9 @@ private Consumption getMeasures(String userId, String prmId, LocalDate from, Loc logger.trace("getData returned {}", data); try { ConsumptionReport report = gson.fromJson(data, ConsumptionReport.class); + if (report == null) { + throw new LinkyException("No report data received"); + } return report.firstLevel.consumptions; } catch (JsonSyntaxException e) { logger.debug("invalid JSON response not matching ConsumptionReport.class: {}", data); diff --git a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/api/ExpiringDayCache.java b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/api/ExpiringDayCache.java index 577120659736..59ec78d57eb8 100644 --- a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/api/ExpiringDayCache.java +++ b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/api/ExpiringDayCache.java @@ -77,24 +77,6 @@ public synchronized Optional getValue() { return Optional.ofNullable(cachedValue); } - /** - * Puts a new value into the cache. - * - * @param value the new value - */ - public final synchronized void putValue(@Nullable V value) { - this.value = value; - expiresAt = calcNextExpiresAt(); - } - - /** - * Invalidates the value in the cache. - */ - public final synchronized void invalidateValue() { - value = null; - expiresAt = calcAlreadyExpired(); - } - /** * Refreshes and returns the value in the cache. * diff --git a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/dto/AuthData.java b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/dto/AuthData.java index 4ee6f93b1e38..bc8407fca4df 100644 --- a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/dto/AuthData.java +++ b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/dto/AuthData.java @@ -12,7 +12,6 @@ */ package org.openhab.binding.linky.internal.dto; -import java.util.ArrayList; import java.util.List; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -31,22 +30,19 @@ public class NameValuePair { public @Nullable Object value; public @Nullable String valueAsString() { - if (value instanceof String) { - return (String) value; - } - return null; + return (value instanceof String) ? (String) value : null; } } public @Nullable String type; - public List output = new ArrayList<>(); - public List input = new ArrayList<>(); + public List output = List.of(); + public List input = List.of(); } public @Nullable String authId; public @Nullable String template; public @Nullable String stage; public @Nullable String header; - public List callbacks = new ArrayList<>(); + public List callbacks = List.of(); } diff --git a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/handler/LinkyHandler.java b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/handler/LinkyHandler.java index 7f4cc7273999..724bd5dd0584 100644 --- a/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/handler/LinkyHandler.java +++ b/bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/handler/LinkyHandler.java @@ -64,11 +64,11 @@ @NonNullByDefault public class LinkyHandler extends BaseThingHandler { - private final Logger logger = LoggerFactory.getLogger(LinkyHandler.class); - private static final int REFRESH_FIRST_HOUR_OF_DAY = 1; private static final int REFRESH_INTERVAL_IN_MIN = 120; + private final Logger logger = LoggerFactory.getLogger(LinkyHandler.class); + private final HttpClient httpClient; private final Gson gson; private final WeekFields weekFields; @@ -146,12 +146,11 @@ public void initialize() { updateStatus(ThingStatus.UNKNOWN); LinkyConfiguration config = getConfigAs(LinkyConfiguration.class); - enedisApi = new EnedisHttpApi(config, gson, httpClient); - - scheduler.submit(() -> { - try { - EnedisHttpApi api = this.enedisApi; - if (api != null) { + if (config.seemsValid()) { + enedisApi = new EnedisHttpApi(config, gson, httpClient); + scheduler.submit(() -> { + try { + EnedisHttpApi api = this.enedisApi; api.initialize(); updateStatus(ThingStatus.ONLINE); @@ -179,13 +178,14 @@ public void initialize() { refreshJob = scheduler.scheduleWithFixedDelay(this::updateData, ChronoUnit.MINUTES.between(now, nextDayFirstTimeUpdate) % REFRESH_INTERVAL_IN_MIN + 1, REFRESH_INTERVAL_IN_MIN, TimeUnit.MINUTES); - } else { - throw new LinkyException("Enedis Api is not initialized"); + } catch (LinkyException e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); } - } catch (LinkyException e) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); - } - }); + }); + } else { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "Username, password and authId are mandatory"); + } } /** @@ -470,7 +470,7 @@ public synchronized void handleCommand(ChannelUID channelUID, Command command) { return consumption; } - public void checkData(Consumption consumption) throws LinkyException { + private void checkData(Consumption consumption) throws LinkyException { if (consumption.aggregats.days.periodes.size() == 0) { throw new LinkyException("invalid consumptions data: no day period"); } diff --git a/bundles/pom.xml b/bundles/pom.xml index c9fa247654a5..3debe4b5ac9e 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -185,9 +185,7 @@ org.openhab.binding.lgtvserial org.openhab.binding.lgwebos org.openhab.binding.lifx - + org.openhab.binding.linky org.openhab.binding.linuxinput org.openhab.binding.lirc org.openhab.binding.logreader