diff --git a/bundles/org.openhab.binding.mybmw/README.md b/bundles/org.openhab.binding.mybmw/README.md index 21f24ec3a620d..8d5ffb24b1b9b 100644 --- a/bundles/org.openhab.binding.mybmw/README.md +++ b/bundles/org.openhab.binding.mybmw/README.md @@ -21,6 +21,8 @@ Please note **this isn't a real-time binding**. If a door is opened the state isn't transmitted and changed immediately. It's not a flaw in the binding itself because the state in BMW's own MyBMW App is also updated with some delay. +This binding does not support China. + ## Supported Things ### Bridge @@ -84,10 +86,9 @@ Properties will be attached to predefined vehicles if the VIN is matching. | password | text | MyBMW Password | | region | text | Select region in order to connect to the appropriate BMW server. | -The region Configuration has 3 different options +The region Configuration has 2 different options - _NORTH_AMERICA_ -- _CHINA_ - _ROW_ (Rest of World) At first initialization, follow the online instructions for login into the BMW API. diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/auth/ChinaAccessToken.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/auth/ChinaAccessToken.java deleted file mode 100644 index cc1f873c3a1cd..0000000000000 --- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/auth/ChinaAccessToken.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (c) 2010-2025 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.mybmw.internal.dto.auth; - -import org.openhab.binding.mybmw.internal.utils.Constants; - -import com.google.gson.annotations.SerializedName; - -/** - * The {@link ChinaAccessToken} Data Transfer Object - * - * @author Bernd Weymann - Initial contribution - */ -public class ChinaAccessToken { - @SerializedName("access_token") - public String accessToken = Constants.EMPTY; - @SerializedName("token_type") - public String tokenType = Constants.EMPTY; -} diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/auth/ChinaPublicKey.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/auth/ChinaPublicKey.java deleted file mode 100644 index 58e17243d47eb..0000000000000 --- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/auth/ChinaPublicKey.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2010-2025 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.mybmw.internal.dto.auth; - -/** - * The {@link ChinaPublicKey} Data Transfer Object - * - * @author Bernd Weymann - Initial contribution - */ -public class ChinaPublicKey { - public String value;// ": "-----BEGIN PUBLIC - // KEY-----\r\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCteEZFIGa2z5cj7sAmX40y8/ige01T2r+VUzkMshAYwotZFvrVWZLQ6W9+ltvINJoRfZEZkmdP2lsidhqj1H1+RWyC78ear7Fm6xd9Gp9LnKtVVBJRM/9cBRg0AGiTJ7IO/x6MpKkBxxHmProFqPI40hueunV85RlaPBrjZVNIpQIDAQAB\r\n-----END - // PUBLIC KEY-----", - public String expires;// ": "3600" -} diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/auth/ChinaPublicKeyResponse.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/auth/ChinaPublicKeyResponse.java deleted file mode 100644 index 466139d2465f2..0000000000000 --- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/auth/ChinaPublicKeyResponse.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2010-2025 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.mybmw.internal.dto.auth; - -/** - * The {@link ChinaPublicKeyResponse} Data Transfer Object - * - * @author Bernd Weymann - Initial contribution - */ -public class ChinaPublicKeyResponse { - public ChinaPublicKey data; - public int code;// ":200, - public String error;// ":false, - public String description;// ":"ok" -} diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/auth/ChinaTokenExpiration.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/auth/ChinaTokenExpiration.java deleted file mode 100644 index c250233d38483..0000000000000 --- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/auth/ChinaTokenExpiration.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2010-2025 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.mybmw.internal.dto.auth; - -/** - * The {@link ChinaTokenExpiration} Data Transfer Object - * - * @author Bernd Weymann - Initial contribution - */ -public class ChinaTokenExpiration { - public String jti;// ":"DUMMY$1$A$1637707916782", - public long nbf;// ":1637707916, - public long exp;// ":1637711216, - public long iat;// ":1637707916} -} diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/auth/ChinaTokenResponse.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/auth/ChinaTokenResponse.java deleted file mode 100644 index 9d3f27876c425..0000000000000 --- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/auth/ChinaTokenResponse.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright (c) 2010-2025 Contributors to the openHAB project - * - * See the NOTICE file(s) distributed with this work for additional - * information. - * - * This program and the accompanying materials are made available under the - * terms of the Eclipse Public License 2.0 which is available at - * http://www.eclipse.org/legal/epl-2.0 - * - * SPDX-License-Identifier: EPL-2.0 - */ -package org.openhab.binding.mybmw.internal.dto.auth; - -/** - * The {@link ChinaTokenResponse} Data Transfer Object - * - * @author Bernd Weymann - Initial contribution - */ -public class ChinaTokenResponse { - public ChinaAccessToken data; - public int code;// ":200, - public String error;// ":false, - public String description;// ":"ok" -} diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/auth/MyBMWTokenController.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/auth/MyBMWTokenController.java index ddb6b173ce65a..3e022835cebd4 100644 --- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/auth/MyBMWTokenController.java +++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/handler/auth/MyBMWTokenController.java @@ -17,19 +17,14 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.security.KeyFactory; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.security.PublicKey; -import java.security.spec.X509EncodedKeySpec; import java.time.Instant; import java.util.Base64; import java.util.UUID; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeoutException; -import javax.crypto.Cipher; - import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; @@ -41,9 +36,6 @@ import org.eclipse.jetty.util.MultiMap; import org.eclipse.jetty.util.UrlEncoded; import org.openhab.binding.mybmw.internal.MyBMWBridgeConfiguration; -import org.openhab.binding.mybmw.internal.dto.auth.ChinaPublicKeyResponse; -import org.openhab.binding.mybmw.internal.dto.auth.ChinaTokenExpiration; -import org.openhab.binding.mybmw.internal.dto.auth.ChinaTokenResponse; import org.openhab.binding.mybmw.internal.dto.auth.OAuthSettingsQueryResponse; import org.openhab.binding.mybmw.internal.handler.MyBMWBridgeHandler; import org.openhab.binding.mybmw.internal.handler.backend.JsonStringDeserializer; @@ -67,6 +59,7 @@ * @author Bernd Weymann - Initial contribution * @author Martin Grassl - extracted from myBmwProxy * @author Mark Herwege - refactor to use OAuthFactory + * @author Mark Herwege - remove China */ @NonNullByDefault public class MyBMWTokenController { @@ -127,28 +120,20 @@ public synchronized AccessTokenResponse getToken() { // reset the token as it times out bridgeHandler.setHCaptchaToken(Constants.EMPTY); } else if (!waitingForInitialToken && tokenResponse.isExpired(Instant.now(), 5)) { - if (REGION_CHINA.equals(bridgeConfiguration.getRegion())) { - // in China no hcaptchatoken is required, so no need to wait for reinitialization - boolean tokenUpdateSuccess = getAndRefreshTokenChina(); - if (!tokenUpdateSuccess) { - logger.warn("Updating token failed!"); - } - } else { - // try to refresh the token - boolean tokenUpdateSuccess = refreshTokenROW(); - logger.trace("update token {}", tokenUpdateSuccess ? "success" : "failed"); - - if (!tokenUpdateSuccess) { - logger.warn("Updating token failed!"); - waitingForInitialToken = true; - - if (bridgeConfiguration.getHCaptchaToken().isBlank()) { - logger.warn( - "initial Authentication failed, request a new captcha token, see https://bimmer-connected.readthedocs.io/en/stable/captcha.html!"); - bridgeHandler.tokenInitError(); - } else { - getToken(); - } + // try to refresh the token + boolean tokenUpdateSuccess = refreshTokenROW(); + logger.trace("update token {}", tokenUpdateSuccess ? "success" : "failed"); + + if (!tokenUpdateSuccess) { + logger.warn("Updating token failed!"); + waitingForInitialToken = true; + + if (bridgeConfiguration.getHCaptchaToken().isBlank()) { + logger.warn( + "initial Authentication failed, request a new captcha token, see https://bimmer-connected.readthedocs.io/en/stable/captcha.html!"); + bridgeHandler.tokenInitError(); + } else { + getToken(); } } } @@ -336,88 +321,4 @@ private String codeFromUrl(String encodedUrl) { }); return codeFound.toString(); } - - /** - * @return true if the token was successfully updated - */ - private boolean getAndRefreshTokenChina() { - try { - /** - * Step 1) get public key - */ - String publicKeyUrl = "https://" + EADRAX_SERVER_MAP.get(REGION_CHINA) + CHINA_PUBLIC_KEY; - Request oauthQueryRequest = httpClient.newRequest(publicKeyUrl); - oauthQueryRequest.header(HttpHeader.USER_AGENT, USER_AGENT); - oauthQueryRequest.header(HEADER_X_USER_AGENT, String.format(X_USER_AGENT, BRAND_BMW, - APP_VERSIONS.get(bridgeConfiguration.getRegion()), bridgeConfiguration.getRegion())); - ContentResponse publicKeyResponse = oauthQueryRequest.send(); - if (publicKeyResponse.getStatus() != 200) { - throw new HttpResponseException("URL: " + oauthQueryRequest.getURI() + ", Error: " - + publicKeyResponse.getStatus() + ", Message: " + publicKeyResponse.getContentAsString(), - publicKeyResponse); - } - ChinaPublicKeyResponse pkr = JsonStringDeserializer - .deserializeString(publicKeyResponse.getContentAsString(), ChinaPublicKeyResponse.class); - - /** - * Step 2) Encode password with public key - */ - // https://www.baeldung.com/java-read-pem-file-keys - String publicKeyStr = pkr.data.value; - String publicKeyPEM = publicKeyStr.replace("-----BEGIN PUBLIC KEY-----", "") - .replaceAll(System.lineSeparator(), "").replace("-----END PUBLIC KEY-----", "").replace("\\r", "") - .replace("\\n", "").trim(); - byte[] encoded = Base64.getDecoder().decode(publicKeyPEM); - X509EncodedKeySpec spec = new X509EncodedKeySpec(encoded); - KeyFactory kf = KeyFactory.getInstance("RSA"); - PublicKey publicKey = kf.generatePublic(spec); - // https://www.thexcoders.net/java-ciphers-rsa/ - Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); - cipher.init(Cipher.ENCRYPT_MODE, publicKey); - byte[] encryptedBytes = cipher.doFinal(bridgeConfiguration.getPassword().getBytes()); - String encodedPassword = Base64.getEncoder().encodeToString(encryptedBytes); - - /** - * Step 3) Send Auth with encoded password - */ - String tokenUrl = "https://" + EADRAX_SERVER_MAP.get(REGION_CHINA) + CHINA_LOGIN; - Request loginRequest = httpClient.POST(tokenUrl); - loginRequest.header(HEADER_X_USER_AGENT, String.format(X_USER_AGENT, BRAND_BMW, - APP_VERSIONS.get(bridgeConfiguration.getRegion()), bridgeConfiguration.getRegion())); - String jsonContent = "{ \"mobile\":\"" + bridgeConfiguration.getUserName() + "\", \"password\":\"" - + encodedPassword + "\"}"; - loginRequest.content(new StringContentProvider(jsonContent)); - Instant tokenCreatedOn = Instant.now(); - ContentResponse tokenResponse = loginRequest.send(); - if (tokenResponse.getStatus() != 200) { - throw new HttpResponseException("URL: " + loginRequest.getURI() + ", Error: " - + tokenResponse.getStatus() + ", Message: " + tokenResponse.getContentAsString(), - tokenResponse); - } - String authCode = getAuthCode(tokenResponse.getContentAsString()); - - /** - * Step 4) Decode access token - */ - ChinaTokenResponse cat = JsonStringDeserializer.deserializeString(authCode, ChinaTokenResponse.class); - String token = cat.data.accessToken; - // https://www.baeldung.com/java-jwt-token-decode - String[] chunks = token.split("\\."); - String tokenJwtDecodeStr = new String(Base64.getUrlDecoder().decode(chunks[1])); - ChinaTokenExpiration cte = JsonStringDeserializer.deserializeString(tokenJwtDecodeStr, - ChinaTokenExpiration.class); - - AccessTokenResponse t = new AccessTokenResponse(); - t.setAccessToken(token); - t.setTokenType(cat.data.tokenType); - t.setCreatedOn(tokenCreatedOn); - t.setExpiresIn(cte.exp); - - this.tokenResponse = t; - return true; - } catch (Exception e) { - logger.warn("Authorization Exception: {}", e.getMessage()); - } - return false; - } } diff --git a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/utils/BimmerConstants.java b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/utils/BimmerConstants.java index 17b7387c50501..c2db61ef5fc29 100644 --- a/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/utils/BimmerConstants.java +++ b/bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/utils/BimmerConstants.java @@ -28,12 +28,12 @@ * * @author Bernd Weymann - Initial contribution * @author Martin Grassl - update to v2 API + * @author Mark Herwege - remove China */ @NonNullByDefault public interface BimmerConstants { static final String REGION_NORTH_AMERICA = "NORTH_AMERICA"; - static final String REGION_CHINA = "CHINA"; static final String REGION_ROW = "ROW"; static final String BRAND_BMW = "bmw"; @@ -46,24 +46,19 @@ public interface BimmerConstants { static final String EADRAX_SERVER_NORTH_AMERICA = "cocoapi.bmwgroup.us"; static final String EADRAX_SERVER_ROW = "cocoapi.bmwgroup.com"; - static final String EADRAX_SERVER_CHINA = "myprofile.bmw.com.cn"; static final Map EADRAX_SERVER_MAP = Map.of(REGION_NORTH_AMERICA, EADRAX_SERVER_NORTH_AMERICA, - REGION_CHINA, EADRAX_SERVER_CHINA, REGION_ROW, EADRAX_SERVER_ROW); + REGION_ROW, EADRAX_SERVER_ROW); static final String OCP_APIM_KEY_NORTH_AMERICA = "31e102f5-6f7e-7ef3-9044-ddce63891362"; static final String OCP_APIM_KEY_ROW = "4f1c85a3-758f-a37d-bbb6-f8704494acfa"; static final Map OCP_APIM_KEYS = Map.of(REGION_NORTH_AMERICA, OCP_APIM_KEY_NORTH_AMERICA, REGION_ROW, OCP_APIM_KEY_ROW); - static final String CHINA_PUBLIC_KEY = "/eadrax-coas/v1/cop/publickey"; - static final String CHINA_LOGIN = "/eadrax-coas/v2/login/pwd"; - // Http variables static final String APP_VERSION_NORTH_AMERICA = "4.9.2(36892)"; static final String APP_VERSION_ROW = "4.9.2(36892)"; - static final String APP_VERSION_CHINA = "4.9.2(36892)"; static final Map APP_VERSIONS = Map.of(REGION_NORTH_AMERICA, APP_VERSION_NORTH_AMERICA, REGION_ROW, - APP_VERSION_ROW, REGION_CHINA, APP_VERSION_CHINA); + APP_VERSION_ROW); static final String USER_AGENT = "Dart/2.16 (dart:io)"; // see const.py of bimmer_constants: user-agent; brand; app_version; region static final String X_USER_AGENT = "android(AP2A.240605.024);%s;%s;%s"; diff --git a/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/config/bridge-config.xml b/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/config/bridge-config.xml index 1ace6c847c213..01a28a925694d 100644 --- a/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/config/bridge-config.xml +++ b/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/config/bridge-config.xml @@ -19,7 +19,6 @@ Select Region in order to connect to the appropriate BMW Server - ROW diff --git a/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/i18n/mybmw.properties b/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/i18n/mybmw.properties index da3efed7c7655..4e8dd5f069f4f 100644 --- a/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/i18n/mybmw.properties +++ b/bundles/org.openhab.binding.mybmw/src/main/resources/OH-INF/i18n/mybmw.properties @@ -12,7 +12,6 @@ thing-type.config.mybmw.bridge.hcaptchatoken.description = Captcha-Token for the thing-type.config.mybmw.bridge.hcaptchatoken.label = Captcha-Token thing-type.config.mybmw.bridge.region.description = Select Region in order to connect to the appropriate BMW Server thing-type.config.mybmw.bridge.region.label = Region -thing-type.config.mybmw.bridge.region.option.CHINA = China thing-type.config.mybmw.bridge.region.option.NORTH_AMERICA = North America thing-type.config.mybmw.bridge.region.option.ROW = Rest of the World thing-type.config.mybmw.bridge.userName.description = MyBMW Username