]> git.basschouten.com Git - openhab-addons.git/commitdiff
[linky] Correcting authentication bug (#11406)
authorGaël L'hopital <gael@lhopital.org>
Thu, 21 Oct 2021 14:22:16 +0000 (16:22 +0200)
committerGitHub <noreply@github.com>
Thu, 21 Oct 2021 14:22:16 +0000 (16:22 +0200)
* Correcting authentication bug (issue #10360)

Signed-off-by: clinique <gael@lhopital.org>
* Reverting PR #11233 & PR #11266

Signed-off-by: Gaël L'hopital <gael@lhopital.org>
* Addressing @lolodomo feed-back

Signed-off-by: Gaël L'hopital <gael@lhopital.org>
* One pointless comment left

Signed-off-by: Gaël L'hopital <gael@lhopital.org>
* Adding missing test on username

Signed-off-by: Gaël L'hopital <gael@lhopital.org>
* Reviewing configuration elements nullness and empty checks.

Signed-off-by: Gaël L'hopital <gael@lhopital.org>
bom/openhab-addons/pom.xml
bundles/org.openhab.binding.linky/src/main/feature/feature.xml [new file with mode: 0644]
bundles/org.openhab.binding.linky/src/main/feature/feature.xml.bak [deleted file]
bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/LinkyConfiguration.java
bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/LinkyHandlerFactory.java
bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/api/EnedisHttpApi.java
bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/api/ExpiringDayCache.java
bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/dto/AuthData.java
bundles/org.openhab.binding.linky/src/main/java/org/openhab/binding/linky/internal/handler/LinkyHandler.java
bundles/pom.xml

index 85e2220f5551fd6140e8291242e6a1538202d32c..89b1ccaa0a9c2f1b0b4e93644ac2f27b3a5988a7 100644 (file)
       <artifactId>org.openhab.binding.lifx</artifactId>
       <version>${project.version}</version>
     </dependency>
-    <!-- linky binding suppressed from the distribution until it is fixed
-      <dependency>
+    <dependency>
       <groupId>org.openhab.addons.bundles</groupId>
       <artifactId>org.openhab.binding.linky</artifactId>
       <version>${project.version}</version>
-      </dependency>
-    -->
+    </dependency>
     <dependency>
       <groupId>org.openhab.addons.bundles</groupId>
       <artifactId>org.openhab.binding.linuxinput</artifactId>
diff --git a/bundles/org.openhab.binding.linky/src/main/feature/feature.xml b/bundles/org.openhab.binding.linky/src/main/feature/feature.xml
new file mode 100644 (file)
index 0000000..20b9446
--- /dev/null
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<features name="org.openhab.binding.linky-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
+       <repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
+
+       <feature name="openhab-binding-linky" description="Linky Binding" version="${project.version}">
+               <feature>openhab-runtime-base</feature>
+               <bundle dependency="true">mvn:org.jsoup/jsoup/1.8.3</bundle>
+               <bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.linky/${project.version}</bundle>
+       </feature>
+</features>
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.bak
deleted file mode 100644 (file)
index 20b9446..0000000
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<features name="org.openhab.binding.linky-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
-       <repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
-
-       <feature name="openhab-binding-linky" description="Linky Binding" version="${project.version}">
-               <feature>openhab-runtime-base</feature>
-               <bundle dependency="true">mvn:org.jsoup/jsoup/1.8.3</bundle>
-               <bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.linky/${project.version}</bundle>
-       </feature>
-</features>
index 5c5029c58f82d8fe4256b1b25b2bb5f7303844bb..f14e2b378a3a80467e859faa12a937ad137b5a25 100644 (file)
  */
 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();
+    }
 }
index d0edaa6a925951ebf464150803171d6a621cced5..1f2e28bdd5eba2fda104f751ff89292ed228f8dc 100644 (file)
@@ -47,9 +47,10 @@ import com.google.gson.JsonDeserializer;
 @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 class LinkyHandlerFactory extends BaseThingHandlerFactory {
         this.localeProvider = localeProvider;
         this.gson = new GsonBuilder().registerTypeAdapter(ZonedDateTime.class,
                 (JsonDeserializer<ZonedDateTime>) (json, type, jsonDeserializationContext) -> ZonedDateTime
-                        .parse(json.getAsJsonPrimitive().getAsString(), formatter))
+                        .parse(json.getAsJsonPrimitive().getAsString(), LINKY_FORMATTER))
                 .create();
         this.httpClient = httpClientFactory.createHttpClient(LinkyBindingConstants.BINDING_ID);
     }
index fb4fad25cb1b1968c262cafbfb6fcaf55731492d..47f964df666aad1c4354b70e2e8c904bcd000e76 100644 (file)
@@ -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 class EnedisHttpApi {
 
             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 class EnedisHttpApi {
         }
     }
 
-    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 class EnedisHttpApi {
     }
 
     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 class EnedisHttpApi {
         }
         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 @@ public class EnedisHttpApi {
         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);
index 577120659736e9395129136e46b48746a263a172..59ec78d57eb86f83bdacc973879d0c1931311e55 100644 (file)
@@ -77,24 +77,6 @@ public class ExpiringDayCache<V> {
         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.
      *
index 4ee6f93b1e38b954a4a54c9599f722c6f6ba3e78..bc8407fca4df02d8e2a4a77e8024aed09173e5a5 100644 (file)
@@ -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 AuthData {
             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<NameValuePair> output = new ArrayList<>();
-        public List<NameValuePair> input = new ArrayList<>();
+        public List<NameValuePair> output = List.of();
+        public List<NameValuePair> input = List.of();
     }
 
     public @Nullable String authId;
     public @Nullable String template;
     public @Nullable String stage;
     public @Nullable String header;
-    public List<AuthDataCallBack> callbacks = new ArrayList<>();
+    public List<AuthDataCallBack> callbacks = List.of();
 }
index 7f4cc7273999ceeeb4078bf64290a39d98f9da54..724bd5dd0584037ec9a1f8dec87c0538399c26de 100644 (file)
@@ -64,11 +64,11 @@ import com.google.gson.Gson;
 
 @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 class LinkyHandler extends BaseThingHandler {
         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 class LinkyHandler extends BaseThingHandler {
                     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 class LinkyHandler extends BaseThingHandler {
         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");
         }
index c9fa247654a58c051dc441b61d1c2dd689b3ba54..3debe4b5ac9e80f5094482d101c032dc481d2e4b 100644 (file)
     <module>org.openhab.binding.lgtvserial</module>
     <module>org.openhab.binding.lgwebos</module>
     <module>org.openhab.binding.lifx</module>
-    <!-- linky binding suppressed from the distribution until it is fixed
-      <module>org.openhab.binding.linky</module>
-    -->
+    <module>org.openhab.binding.linky</module>
     <module>org.openhab.binding.linuxinput</module>
     <module>org.openhab.binding.lirc</module>
     <module>org.openhab.binding.logreader</module>