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

[somfytahoma] Switch Tahoma to OAUTH2 authentication #17361

Merged
merged 1 commit into from
Oct 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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 @@ -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";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,8 @@ public class SomfyTahomaBridgeHandler extends BaseBridgeHandler {

private String localToken = "";

private String accessToken = "";

private Map<String, SomfyTahomaDevice> devicePlaces = new HashMap<>();

private ExpiringCache<List<SomfyTahomaDevice>> cachedDevices = new ExpiringCache<>(Duration.ofSeconds(30),
Expand Down Expand Up @@ -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);
Expand All @@ -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;
}
Expand Down Expand Up @@ -507,6 +503,7 @@ private void cleanup() {

// Clean access data
localToken = "";
accessToken = "";
}

@Override
Expand Down Expand Up @@ -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) {
Expand All @@ -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;

Expand Down Expand Up @@ -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
lolodomo marked this conversation as resolved.
Show resolved Hide resolved
+ "&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
Expand Down Expand Up @@ -1036,6 +1075,7 @@ private boolean reLogin() {
logger.debug("Doing relogin");
reLoginNeeded = true;
localToken = "";
accessToken = "";
login();
return ThingStatus.OFFLINE != thing.getStatus();
}
Expand Down