Skip to content

Commit 970ca59

Browse files
authoredOct 15, 2024
[somfytahoma] switch Tahoma to OAUTH2 authentication (openhab#17361)
Signed-off-by: Ondrej Pecta <opecta@gmail.com>
1 parent 0c855ff commit 970ca59

File tree

2 files changed

+93
-50
lines changed

2 files changed

+93
-50
lines changed
 

‎bundles/org.openhab.binding.somfytahoma/src/main/java/org/openhab/binding/somfytahoma/internal/SomfyTahomaBindingConstants.java

+3
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,9 @@ public class SomfyTahomaBindingConstants {
371371
// Constants
372372
public static final String COZYTOUCH_PORTAL = "ha110-1.overkiz.com";
373373
public static final String TAHOMA_PORTAL = "www.tahomalink.com";
374+
public static final String SOMFY_OAUTH2_URL = "accounts.somfy.com/oauth/oauth/v2/token";
375+
public static final String SOMFY_OAUTH2_CLIENT_ID = "1e2d830f-4c65-11e7-bd0c-02dd59bd3041_5n78r5nnwaw4wc0kskkg0csogkk8cwocswg84c0gowcgossogw";
376+
public static final String SOMFY_OAUTH2_CLIENT_SECRET = "4txucwsv29a8o0co8s8kw8ggswkks8ossccockgcckokw8ck00";
374377
public static final String COZYTOUCH_OAUTH2_URL = "api.groupe-atlantic.com";
375378
public static final String COZYTOUCH_OAUTH2_BASICAUTH = "czduc0RZZXdWbjVGbVV4UmlYN1pVSUM3ZFI4YTphSDEzOXZmbzA1ZGdqeDJkSFVSQkFTbmhCRW9h";
376379
public static final String COZYTOUCH_OAUTH2_TOKEN_URL = "/token";

‎bundles/org.openhab.binding.somfytahoma/src/main/java/org/openhab/binding/somfytahoma/internal/handler/SomfyTahomaBridgeHandler.java

+90-50
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,8 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler {
158158

159159
private String localToken = "";
160160

161+
private String accessToken = "";
162+
161163
private Map<String, SomfyTahomaDevice> devicePlaces = new HashMap<>();
162164

163165
private ExpiringCache<List<SomfyTahomaDevice>> cachedDevices = new ExpiringCache<>(Duration.ofSeconds(30),
@@ -261,62 +263,29 @@ public synchronized void login() {
261263
cloudFallback = false;
262264

263265
try {
264-
String urlParameters = "";
266+
lastLoginTimestamp = Instant.now();
265267

266-
// if cozytouch, must use oauth server
267268
if (thingConfig.getCloudPortal().equalsIgnoreCase(COZYTOUCH_PORTAL)) {
268-
logger.debug("CozyTouch Oauth2 authentication flow");
269-
urlParameters = "jwt=" + loginCozytouch();
269+
if (!loginCozyTouch()) {
270+
return;
271+
}
270272
} else {
271-
urlParameters = "userId=" + urlEncode(thingConfig.getEmail()) + "&userPassword="
272-
+ urlEncode(thingConfig.getPassword());
273+
loginOAUTH();
273274
}
274275

275-
ContentResponse response = sendRequestBuilder("login", HttpMethod.POST)
276-
.content(new StringContentProvider(urlParameters),
277-
"application/x-www-form-urlencoded; charset=UTF-8")
278-
.send();
279-
280-
if (logger.isTraceEnabled()) {
281-
logger.trace("Login response: {}", response.getContentAsString());
276+
if (thingConfig.isDevMode()) {
277+
initializeLocalMode();
282278
}
283279

284-
SomfyTahomaLoginResponse data = gson.fromJson(response.getContentAsString(),
285-
SomfyTahomaLoginResponse.class);
286-
287-
lastLoginTimestamp = Instant.now();
288-
289-
if (data == null) {
290-
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
291-
"Received invalid data (login)");
292-
} else if (!data.getErrorCode().isEmpty()) {
293-
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, data.getError());
294-
if (data.getError().startsWith(TOO_MANY_REQUESTS)) {
295-
setTooManyRequests();
296-
}
280+
String id = registerEvents();
281+
if (id != null && !UNAUTHORIZED.equals(id)) {
282+
eventsId = id;
283+
logger.debug("Events id: {}", eventsId);
284+
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE,
285+
isDevModeReady() ? "LAN mode" : cloudFallback ? "Cloud mode fallback" : "Cloud mode");
297286
} else {
298-
if (thingConfig.isDevMode()) {
299-
initializeLocalMode();
300-
}
301-
302-
String id = registerEvents();
303-
if (id != null && !UNAUTHORIZED.equals(id)) {
304-
eventsId = id;
305-
logger.debug("Events id: {}", eventsId);
306-
updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE,
307-
isDevModeReady() ? "LAN mode" : cloudFallback ? "Cloud mode fallback" : "Cloud mode");
308-
} else {
309-
logger.debug("Events id error: {}", id);
310-
if (!thingConfig.isDevMode()) {
311-
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
312-
"unable to register events");
313-
} else {
314-
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
315-
"LAN mode is not properly configured");
316-
logger.debug("Forcing the gateway discovery");
317-
discoverGateway();
318-
}
319-
}
287+
logger.debug("Events id error: {}", id);
288+
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "unable to register events");
320289
}
321290
} catch (JsonSyntaxException e) {
322291
logger.debug("Received invalid data (login)", e);
@@ -339,6 +308,33 @@ public synchronized void login() {
339308
}
340309
}
341310

311+
private boolean loginCozyTouch()
312+
throws ExecutionException, InterruptedException, TimeoutException, JsonSyntaxException {
313+
logger.debug("CozyTouch Oauth2 authentication flow");
314+
String urlParameters = "jwt=" + getCozytouchJWT();
315+
ContentResponse response = sendRequestBuilder("login", HttpMethod.POST)
316+
.content(new StringContentProvider(urlParameters), "application/x-www-form-urlencoded; charset=UTF-8")
317+
.send();
318+
319+
if (logger.isTraceEnabled()) {
320+
logger.trace("Login response: {}", response.getContentAsString());
321+
}
322+
323+
SomfyTahomaLoginResponse data = gson.fromJson(response.getContentAsString(), SomfyTahomaLoginResponse.class);
324+
325+
if (data == null) {
326+
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Received invalid data (login)");
327+
return false;
328+
} else if (!data.getErrorCode().isEmpty()) {
329+
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, data.getError());
330+
if (data.getError().startsWith(TOO_MANY_REQUESTS)) {
331+
setTooManyRequests();
332+
}
333+
return false;
334+
}
335+
return true;
336+
}
337+
342338
public boolean isDevModeReady() {
343339
return thingConfig.isDevMode() && !localToken.isEmpty() && !cloudFallback;
344340
}
@@ -507,6 +503,7 @@ private void cleanup() {
507503

508504
// Clean access data
509505
localToken = "";
506+
accessToken = "";
510507
}
511508

512509
@Override
@@ -852,10 +849,16 @@ private boolean isLocalRequest(String subUrl) {
852849
}
853850

854851
private Request sendRequestBuilderCloud(String subUrl, HttpMethod method) {
855-
return httpClient.newRequest(getApiFullUrl(subUrl)).method(method)
852+
Request request = httpClient.newRequest(getApiFullUrl(subUrl)).method(method)
856853
.header(HttpHeader.ACCEPT_LANGUAGE, "en-US,en").header(HttpHeader.ACCEPT_ENCODING, "gzip, deflate")
857854
.header("X-Requested-With", "XMLHttpRequest").timeout(TAHOMA_TIMEOUT, TimeUnit.SECONDS)
858855
.agent(TAHOMA_AGENT);
856+
857+
if (!thingConfig.getCloudPortal().equalsIgnoreCase(COZYTOUCH_PORTAL)) {
858+
// user OAuth token if not cozytouch
859+
request = request.header(HttpHeader.AUTHORIZATION, "Bearer " + accessToken);
860+
}
861+
return request;
859862
}
860863

861864
private Request sendRequestBuilderLocal(String subUrl, HttpMethod method) {
@@ -872,7 +875,7 @@ private Request sendRequestBuilderLocal(String subUrl, HttpMethod method) {
872875
* @throws InterruptedException
873876
* @throws JsonSyntaxException
874877
*/
875-
private String loginCozytouch()
878+
private String getCozytouchJWT()
876879
throws InterruptedException, TimeoutException, ExecutionException, JsonSyntaxException {
877880
String authBaseUrl = "https://" + COZYTOUCH_OAUTH2_URL;
878881

@@ -920,6 +923,42 @@ private String loginCozytouch()
920923
}
921924
}
922925

926+
private void loginOAUTH() throws InterruptedException, TimeoutException, ExecutionException, JsonSyntaxException {
927+
String authBaseUrl = "https://" + SOMFY_OAUTH2_URL;
928+
929+
String urlParameters = "client_id=" + SOMFY_OAUTH2_CLIENT_ID + "&client_secret=" + SOMFY_OAUTH2_CLIENT_SECRET
930+
+ "&grant_type=password&username=" + urlEncode(thingConfig.getEmail()) + "&password="
931+
+ urlEncode(thingConfig.getPassword());
932+
933+
ContentResponse response = httpClient.newRequest(authBaseUrl).method(HttpMethod.POST)
934+
.header(HttpHeader.ACCEPT_LANGUAGE, "en-US,en").header(HttpHeader.ACCEPT_ENCODING, "gzip, deflate")
935+
.header("X-Requested-With", "XMLHttpRequest").timeout(TAHOMA_TIMEOUT, TimeUnit.SECONDS)
936+
.agent(TAHOMA_AGENT)
937+
.content(new StringContentProvider(urlParameters), "application/x-www-form-urlencoded; charset=UTF-8")
938+
.send();
939+
940+
if (response.getStatus() != 200) {
941+
// Login error
942+
if (response.getHeaders().getField(HttpHeader.CONTENT_TYPE).getValue()
943+
.equalsIgnoreCase(MediaType.APPLICATION_JSON)) {
944+
try {
945+
SomfyTahomaOauth2Error error = gson.fromJson(response.getContentAsString(),
946+
SomfyTahomaOauth2Error.class);
947+
throw new ExecutionException(error.getErrorDescription(), null);
948+
} catch (JsonSyntaxException e) {
949+
}
950+
}
951+
throw new ExecutionException("Unknown error while attempting to log in.", null);
952+
}
953+
954+
SomfyTahomaOauth2Reponse oauth2response = gson.fromJson(response.getContentAsString(),
955+
SomfyTahomaOauth2Reponse.class);
956+
957+
logger.debug("OAuth2 Access Token: {}", oauth2response.getAccessToken());
958+
959+
accessToken = oauth2response.getAccessToken();
960+
}
961+
923962
private String getApiFullUrl(String subUrl) {
924963
return isLocalRequest(subUrl)
925964
? "https://" + thingConfig.getIp() + ":8443/enduser-mobile-web/1/enduserAPI/" + subUrl
@@ -1036,6 +1075,7 @@ private boolean reLogin() {
10361075
logger.debug("Doing relogin");
10371076
reLoginNeeded = true;
10381077
localToken = "";
1078+
accessToken = "";
10391079
login();
10401080
return ThingStatus.OFFLINE != thing.getStatus();
10411081
}

0 commit comments

Comments
 (0)