Skip to content

Commit

Permalink
Correcting authentication bug (issue openhab#10360)
Browse files Browse the repository at this point in the history
Signed-off-by: clinique <gael@lhopital.org>
  • Loading branch information
clinique committed Oct 18, 2021
1 parent a117073 commit 9f80bea
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 67 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,19 @@
*/
package org.openhab.binding.linky.internal;

import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;

/**
* 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 @Nullable String username;
public @Nullable String password;
public @Nullable String internalAuthId;
}
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 @@ -74,9 +74,14 @@ public EnedisHttpApi(LinkyConfiguration config, Gson gson, HttpClient httpClient
}

public void initialize() throws LinkyException {
addCookie(LinkyConfiguration.INTERNAL_AUTH_ID, config.internalAuthId);
String authId = config.internalAuthId;
String username = config.username;
if (authId == null || username == null) {
throw new LinkyException("username and internalAuthId are mandatory");
}
addCookie(LinkyConfiguration.INTERNAL_AUTH_ID, authId);

logger.debug("Starting login process for user : {}", config.username);
logger.debug("Starting login process for user : {}", username);

try {
logger.debug("Step 1 : getting authentification");
Expand Down Expand Up @@ -109,14 +114,15 @@ 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.size() == 0
|| authData.callbacks.get(1).input.size() == 0 || !username
.equals(Objects.requireNonNull(authData.callbacks.get(0).input.get(0)).valueAsString())) {
throw new LinkyException("Authentication error, the authentication_cookie is probably wrong");
}
Expand All @@ -128,12 +134,18 @@ public void initialize() throws LinkyException {

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,11 +167,11 @@ 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
Expand Down Expand Up @@ -220,6 +232,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 +274,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 @@ -151,37 +151,33 @@ public void initialize() {
scheduler.submit(() -> {
try {
EnedisHttpApi api = this.enedisApi;
if (api != null) {
api.initialize();
updateStatus(ThingStatus.ONLINE);

if (thing.getProperties().isEmpty()) {
Map<String, String> properties = new HashMap<>();
PrmInfo prmInfo = api.getPrmInfo();
UserInfo userInfo = api.getUserInfo();
properties.put(USER_ID, userInfo.userProperties.internId);
properties.put(PUISSANCE, prmInfo.puissanceSouscrite + " kVA");
properties.put(PRM_ID, prmInfo.prmId);
updateProperties(properties);
}
api.initialize();
updateStatus(ThingStatus.ONLINE);

prmId = thing.getProperties().get(PRM_ID);
userId = thing.getProperties().get(USER_ID);
if (thing.getProperties().isEmpty()) {
Map<String, String> properties = new HashMap<>();
PrmInfo prmInfo = api.getPrmInfo();
UserInfo userInfo = api.getUserInfo();
properties.put(USER_ID, userInfo.userProperties.internId);
properties.put(PUISSANCE, prmInfo.puissanceSouscrite + " kVA");
properties.put(PRM_ID, prmInfo.prmId);
updateProperties(properties);
}

updateData();
prmId = thing.getProperties().get(PRM_ID);
userId = thing.getProperties().get(USER_ID);

disconnect();
updateData();

final LocalDateTime now = LocalDateTime.now();
final LocalDateTime nextDayFirstTimeUpdate = now.plusDays(1).withHour(REFRESH_FIRST_HOUR_OF_DAY)
.truncatedTo(ChronoUnit.HOURS);
disconnect();

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");
}
final LocalDateTime now = LocalDateTime.now();
final LocalDateTime nextDayFirstTimeUpdate = now.plusDays(1).withHour(REFRESH_FIRST_HOUR_OF_DAY)
.truncatedTo(ChronoUnit.HOURS);

refreshJob = scheduler.scheduleWithFixedDelay(this::updateData,
ChronoUnit.MINUTES.between(now, nextDayFirstTimeUpdate) % REFRESH_INTERVAL_IN_MIN + 1,
REFRESH_INTERVAL_IN_MIN, TimeUnit.MINUTES);
} catch (LinkyException e) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
Expand Down Expand Up @@ -470,7 +466,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

0 comments on commit 9f80bea

Please sign in to comment.