diff --git a/bundles/org.openhab.binding.somfytahoma/src/main/java/org/openhab/binding/somfytahoma/internal/SomfyTahomaBindingConstants.java b/bundles/org.openhab.binding.somfytahoma/src/main/java/org/openhab/binding/somfytahoma/internal/SomfyTahomaBindingConstants.java index 03a417dfabaa3..a514893b6c96c 100644 --- a/bundles/org.openhab.binding.somfytahoma/src/main/java/org/openhab/binding/somfytahoma/internal/SomfyTahomaBindingConstants.java +++ b/bundles/org.openhab.binding.somfytahoma/src/main/java/org/openhab/binding/somfytahoma/internal/SomfyTahomaBindingConstants.java @@ -371,6 +371,9 @@ public class SomfyTahomaBindingConstants { // Constants public static final String COZYTOUCH_PORTAL = "ha110-1.overkiz.com"; public static final String TAHOMA_PORTAL = "www.tahomalink.com"; + public static final String SOMFY_OAUTH2_URL = "accounts.somfy.com/oauth/oauth/v2/token"; + public static final String SOMFY_OAUTH2_CLIENT_ID = "1e2d830f-4c65-11e7-bd0c-02dd59bd3041_5n78r5nnwaw4wc0kskkg0csogkk8cwocswg84c0gowcgossogw"; + public static final String SOMFY_OAUTH2_CLIENT_SECRET = "4txucwsv29a8o0co8s8kw8ggswkks8ossccockgcckokw8ck00"; public static final String COZYTOUCH_OAUTH2_URL = "api.groupe-atlantic.com"; public static final String COZYTOUCH_OAUTH2_BASICAUTH = "czduc0RZZXdWbjVGbVV4UmlYN1pVSUM3ZFI4YTphSDEzOXZmbzA1ZGdqeDJkSFVSQkFTbmhCRW9h"; public static final String COZYTOUCH_OAUTH2_TOKEN_URL = "/token"; diff --git a/bundles/org.openhab.binding.somfytahoma/src/main/java/org/openhab/binding/somfytahoma/internal/handler/SomfyTahomaBridgeHandler.java b/bundles/org.openhab.binding.somfytahoma/src/main/java/org/openhab/binding/somfytahoma/internal/handler/SomfyTahomaBridgeHandler.java index babfd7ca92f17..636217d73beb8 100644 --- a/bundles/org.openhab.binding.somfytahoma/src/main/java/org/openhab/binding/somfytahoma/internal/handler/SomfyTahomaBridgeHandler.java +++ b/bundles/org.openhab.binding.somfytahoma/src/main/java/org/openhab/binding/somfytahoma/internal/handler/SomfyTahomaBridgeHandler.java @@ -158,6 +158,8 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler { private String localToken = ""; + private String accessToken = ""; + private Map devicePlaces = new HashMap<>(); private ExpiringCache> cachedDevices = new ExpiringCache<>(Duration.ofSeconds(30), @@ -261,62 +263,29 @@ public synchronized void login() { cloudFallback = false; try { - String urlParameters = ""; + lastLoginTimestamp = Instant.now(); - // if cozytouch, must use oauth server if (thingConfig.getCloudPortal().equalsIgnoreCase(COZYTOUCH_PORTAL)) { - logger.debug("CozyTouch Oauth2 authentication flow"); - urlParameters = "jwt=" + loginCozytouch(); + if (!loginCozyTouch()) { + return; + } } else { - urlParameters = "userId=" + urlEncode(thingConfig.getEmail()) + "&userPassword=" - + urlEncode(thingConfig.getPassword()); + loginOAUTH(); } - ContentResponse response = sendRequestBuilder("login", HttpMethod.POST) - .content(new StringContentProvider(urlParameters), - "application/x-www-form-urlencoded; charset=UTF-8") - .send(); - - if (logger.isTraceEnabled()) { - logger.trace("Login response: {}", response.getContentAsString()); + if (thingConfig.isDevMode()) { + initializeLocalMode(); } - SomfyTahomaLoginResponse data = gson.fromJson(response.getContentAsString(), - SomfyTahomaLoginResponse.class); - - lastLoginTimestamp = Instant.now(); - - if (data == null) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, - "Received invalid data (login)"); - } else if (!data.getErrorCode().isEmpty()) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, data.getError()); - if (data.getError().startsWith(TOO_MANY_REQUESTS)) { - setTooManyRequests(); - } + String id = registerEvents(); + if (id != null && !UNAUTHORIZED.equals(id)) { + eventsId = id; + logger.debug("Events id: {}", eventsId); + updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, + isDevModeReady() ? "LAN mode" : cloudFallback ? "Cloud mode fallback" : "Cloud mode"); } else { - if (thingConfig.isDevMode()) { - initializeLocalMode(); - } - - String id = registerEvents(); - if (id != null && !UNAUTHORIZED.equals(id)) { - eventsId = id; - logger.debug("Events id: {}", eventsId); - updateStatus(ThingStatus.ONLINE, ThingStatusDetail.NONE, - isDevModeReady() ? "LAN mode" : cloudFallback ? "Cloud mode fallback" : "Cloud mode"); - } else { - logger.debug("Events id error: {}", id); - if (!thingConfig.isDevMode()) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, - "unable to register events"); - } else { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, - "LAN mode is not properly configured"); - logger.debug("Forcing the gateway discovery"); - discoverGateway(); - } - } + logger.debug("Events id error: {}", id); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "unable to register events"); } } catch (JsonSyntaxException e) { logger.debug("Received invalid data (login)", e); @@ -339,6 +308,33 @@ public synchronized void login() { } } + private boolean loginCozyTouch() + throws ExecutionException, InterruptedException, TimeoutException, JsonSyntaxException { + logger.debug("CozyTouch Oauth2 authentication flow"); + String urlParameters = "jwt=" + getCozytouchJWT(); + ContentResponse response = sendRequestBuilder("login", HttpMethod.POST) + .content(new StringContentProvider(urlParameters), "application/x-www-form-urlencoded; charset=UTF-8") + .send(); + + if (logger.isTraceEnabled()) { + logger.trace("Login response: {}", response.getContentAsString()); + } + + SomfyTahomaLoginResponse data = gson.fromJson(response.getContentAsString(), SomfyTahomaLoginResponse.class); + + if (data == null) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Received invalid data (login)"); + return false; + } else if (!data.getErrorCode().isEmpty()) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, data.getError()); + if (data.getError().startsWith(TOO_MANY_REQUESTS)) { + setTooManyRequests(); + } + return false; + } + return true; + } + public boolean isDevModeReady() { return thingConfig.isDevMode() && !localToken.isEmpty() && !cloudFallback; } @@ -507,6 +503,7 @@ private void cleanup() { // Clean access data localToken = ""; + accessToken = ""; } @Override @@ -852,10 +849,16 @@ private boolean isLocalRequest(String subUrl) { } private Request sendRequestBuilderCloud(String subUrl, HttpMethod method) { - return httpClient.newRequest(getApiFullUrl(subUrl)).method(method) + Request request = httpClient.newRequest(getApiFullUrl(subUrl)).method(method) .header(HttpHeader.ACCEPT_LANGUAGE, "en-US,en").header(HttpHeader.ACCEPT_ENCODING, "gzip, deflate") .header("X-Requested-With", "XMLHttpRequest").timeout(TAHOMA_TIMEOUT, TimeUnit.SECONDS) .agent(TAHOMA_AGENT); + + if (!thingConfig.getCloudPortal().equalsIgnoreCase(COZYTOUCH_PORTAL)) { + // user OAuth token if not cozytouch + request = request.header(HttpHeader.AUTHORIZATION, "Bearer " + accessToken); + } + return request; } private Request sendRequestBuilderLocal(String subUrl, HttpMethod method) { @@ -872,7 +875,7 @@ private Request sendRequestBuilderLocal(String subUrl, HttpMethod method) { * @throws InterruptedException * @throws JsonSyntaxException */ - private String loginCozytouch() + private String getCozytouchJWT() throws InterruptedException, TimeoutException, ExecutionException, JsonSyntaxException { String authBaseUrl = "https://" + COZYTOUCH_OAUTH2_URL; @@ -920,6 +923,42 @@ private String loginCozytouch() } } + private void loginOAUTH() throws InterruptedException, TimeoutException, ExecutionException, JsonSyntaxException { + String authBaseUrl = "https://" + SOMFY_OAUTH2_URL; + + String urlParameters = "client_id=" + SOMFY_OAUTH2_CLIENT_ID + "&client_secret=" + SOMFY_OAUTH2_CLIENT_SECRET + + "&grant_type=password&username=" + urlEncode(thingConfig.getEmail()) + "&password=" + + urlEncode(thingConfig.getPassword()); + + ContentResponse response = httpClient.newRequest(authBaseUrl).method(HttpMethod.POST) + .header(HttpHeader.ACCEPT_LANGUAGE, "en-US,en").header(HttpHeader.ACCEPT_ENCODING, "gzip, deflate") + .header("X-Requested-With", "XMLHttpRequest").timeout(TAHOMA_TIMEOUT, TimeUnit.SECONDS) + .agent(TAHOMA_AGENT) + .content(new StringContentProvider(urlParameters), "application/x-www-form-urlencoded; charset=UTF-8") + .send(); + + if (response.getStatus() != 200) { + // Login error + if (response.getHeaders().getField(HttpHeader.CONTENT_TYPE).getValue() + .equalsIgnoreCase(MediaType.APPLICATION_JSON)) { + try { + SomfyTahomaOauth2Error error = gson.fromJson(response.getContentAsString(), + SomfyTahomaOauth2Error.class); + throw new ExecutionException(error.getErrorDescription(), null); + } catch (JsonSyntaxException e) { + } + } + throw new ExecutionException("Unknown error while attempting to log in.", null); + } + + SomfyTahomaOauth2Reponse oauth2response = gson.fromJson(response.getContentAsString(), + SomfyTahomaOauth2Reponse.class); + + logger.debug("OAuth2 Access Token: {}", oauth2response.getAccessToken()); + + accessToken = oauth2response.getAccessToken(); + } + private String getApiFullUrl(String subUrl) { return isLocalRequest(subUrl) ? "https://" + thingConfig.getIp() + ":8443/enduser-mobile-web/1/enduserAPI/" + subUrl @@ -1036,6 +1075,7 @@ private boolean reLogin() { logger.debug("Doing relogin"); reLoginNeeded = true; localToken = ""; + accessToken = ""; login(); return ThingStatus.OFFLINE != thing.getStatus(); }