Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[linky] Correcting authentication bug #11406

Merged
merged 6 commits into from
Oct 21, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
lolodomo marked this conversation as resolved.
Show resolved Hide resolved
}
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
lolodomo marked this conversation as resolved.
Show resolved Hide resolved
.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