Skip to content

Commit

Permalink
[linky] Correcting authentication bug (openhab#11406)
Browse files Browse the repository at this point in the history
* Correcting authentication bug (issue openhab#10360)

Signed-off-by: clinique <gael@lhopital.org>

* Reverting PR openhab#11233 & PR openhab#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>
  • Loading branch information
clinique authored and volkmarnissen committed Feb 17, 2022
1 parent 396ca90 commit 6492dc3
Show file tree
Hide file tree
Showing 9 changed files with 59 additions and 64 deletions.
6 changes: 2 additions & 4 deletions bom/openhab-addons/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -751,13 +751,11 @@
<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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -60,7 +61,7 @@ public LinkyHandlerFactory(final @Reference LocaleProvider localeProvider,
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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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);

Expand All @@ -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) {
Expand All @@ -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("/");
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,24 +77,6 @@ public synchronized Optional<V> 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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<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();
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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");
}
}

/**
Expand Down Expand Up @@ -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");
}
Expand Down
4 changes: 1 addition & 3 deletions bundles/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -185,9 +185,7 @@
<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>
Expand Down

0 comments on commit 6492dc3

Please sign in to comment.