Skip to content

Commit 3b820ed

Browse files
authored
[mybmw] improve authentication (openhab#18235)
* authentication Signed-off-by: Mark Herwege <[email protected]>
1 parent ca8d10f commit 3b820ed

36 files changed

+974
-1293
lines changed

bundles/org.openhab.binding.mybmw/README.md

+17-6
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ Please note **this isn't a real-time binding**.
2121
If a door is opened the state isn't transmitted and changed immediately.
2222
It's not a flaw in the binding itself because the state in BMW's own MyBMW App is also updated with some delay.
2323

24+
This binding does not support the region: China.
25+
2426
## Supported Things
2527

2628
### Bridge
@@ -82,26 +84,35 @@ Properties will be attached to predefined vehicles if the VIN is matching.
8284
|-----------------|---------|--------------------------------------------------------------------------------------------------------|
8385
| userName | text | MyBMW Username |
8486
| password | text | MyBMW Password |
85-
| hcaptchatoken | text | HCaptcha-Token for initial login (see https://bimmer-connected.readthedocs.io/en/latest/captcha.html) |
8687
| region | text | Select region in order to connect to the appropriate BMW server. |
8788

88-
The region Configuration has 3 different options
89+
The region Configuration has 2 different options
8990

9091
- _NORTH_AMERICA_
91-
- _CHINA_
9292
- _ROW_ (Rest of World)
9393

94+
At first initialization, follow the online instructions for login into the BMW API.
95+
9496
#### Advanced Configuration
9597

96-
| Parameter | Type | Description |
97-
|-----------------|---------|---------------------------------------------------------|
98-
| language | text | Channel data can be returned in the desired language |
98+
| Parameter | Type | Description |
99+
|-----------------|---------|--------------------------------------------------------------------------------------------------------|
100+
| language | text | Channel data can be returned in the desired language |
101+
| hcaptchatoken | text | HCaptcha-Token for initial login (see https://bimmer-connected.readthedocs.io/en/stable/captcha.html) |
102+
| callbackIP | text | IP address for openHAB callback URL, defaults to IP of openHAB host |
103+
| callbackPort | integer | Port Number for openHAB callback URL, default 8090 |
99104

100105
Language is predefined as _AUTODETECT_.
101106
Some textual descriptions, date and times are delivered based on your local language.
102107
You can overwrite this setting with lowercase 2-letter [language code reagrding ISO 639](https://www.oracle.com/java/technologies/javase/jdk8-jre8-suported-locales.html)
103108
So if want your UI in english language place _en_ as desired language.
104109

110+
The initial login to the BMW API requires a Captcha Token.
111+
At first configuration, you can set the Captcha Token as a configuration parameter manually.
112+
113+
To set the Captcha Token online, a webpage is presented and a callback to the bridge is created temporarily on the hosts IP address and a default port.
114+
If the port is already in use, or you have a complex network setup, you may have to override the defaults provided.
115+
105116
### Thing Configuration
106117

107118
Same configuration is needed for all things

bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/MyBMWBridgeConfiguration.java

+31-3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
*
2121
* @author Bernd Weymann - Initial contribution
2222
* @author Martin Grassl - renamed and added hcaptchastring
23+
* @author Mark Herwege - added authorisation servlet
2324
*/
2425
@NonNullByDefault
2526
public class MyBMWBridgeConfiguration {
@@ -49,6 +50,16 @@ public class MyBMWBridgeConfiguration {
4950
*/
5051
private String hcaptchatoken = Constants.EMPTY;
5152

53+
/**
54+
* the callback IP address for the authorisation servlet
55+
*/
56+
private String callbackIP = Constants.EMPTY;
57+
58+
/**
59+
* the callback port for the authorisation servlet
60+
*/
61+
private int callbackPort = 8090;
62+
5263
public String getRegion() {
5364
return region;
5465
}
@@ -81,17 +92,34 @@ public void setLanguage(String language) {
8192
this.language = language;
8293
}
8394

84-
public String getHcaptchatoken() {
95+
public String getHCaptchaToken() {
8596
return hcaptchatoken;
8697
}
8798

88-
public void setHcaptchatoken(String hcaptchatoken) {
99+
public void setHCaptchaToken(String hcaptchatoken) {
89100
this.hcaptchatoken = hcaptchatoken;
90101
}
91102

103+
public String getCallbackIP() {
104+
return Constants.EMPTY.equals(callbackIP) ? "" : callbackIP;
105+
}
106+
107+
public void setCallbackIP(String callbackIP) {
108+
this.callbackIP = callbackIP;
109+
}
110+
111+
public int getCallbackPort() {
112+
return callbackPort;
113+
}
114+
115+
public void setCallbackPort(int callbackPort) {
116+
this.callbackPort = callbackPort;
117+
}
118+
92119
@Override
93120
public String toString() {
94121
return "MyBMWBridgeConfiguration [region=" + region + ", userName=" + userName + ", password=" + password
95-
+ ", language=" + language + ", hcaptchatoken=" + hcaptchatoken + "]";
122+
+ ", language=" + language + ", hcaptchatoken=" + hcaptchatoken + ", callbackAddress=" + callbackIP
123+
+ ":" + callbackPort + "]";
96124
}
97125
}

bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/MyBMWConstants.java

+18
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
*/
1313
package org.openhab.binding.mybmw.internal;
1414

15+
import java.util.Map;
1516
import java.util.Set;
1617

1718
import org.eclipse.jdt.annotation.NonNullByDefault;
@@ -60,6 +61,23 @@ public interface MyBMWConstants {
6061

6162
static final int DEFAULT_REFRESH_INTERVAL_MINUTES = 60;
6263

64+
// Captcha servlet constants
65+
static final String LOCAL_OPENHAB_BASE_PATH = "/" + BINDING_ID + "/";
66+
static final String CAPTCHA_URL_ROOT = "captcha/";
67+
static final String NORTH_AMERICA = "NORTH_AMERICA";
68+
static final String ROW = "ROW";
69+
static final Map<String, String> CAPTCHA_HTML = Map.of(NORTH_AMERICA, CAPTCHA_URL_ROOT + "north_america_form.html",
70+
ROW, CAPTCHA_URL_ROOT + "rest_of_world_form.html");
71+
72+
// Thing status detail messages
73+
static final String STATUS_AUTH_NEEDED = "@text/mybmw.status.authorization-needed";
74+
static final String STATUS_USER_DETAILS_MISSING = "@text/mybmw.status.user-details-missing";
75+
static final String STATUS_REGION_MISSING = "@text/mybmw.status.region-missing";
76+
static final String STATUS_IP_MISSING = "@text/mybmw.status.ip-missing";
77+
static final String STATUS_VEHICLE_RETRIEVAL_ERROR = "@text/mybmw.status.vehicle-retrieval-error";
78+
static final String STATUS_NETWORK_ERROR = "@text/mybmw.status.network-error";
79+
static final String STATUS_QUOTA_ERROR = "@text/mybmw.status.quota-error";
80+
6381
// See constants from bimmer-connected
6482
// https://github.com/bimmerconnected/bimmer_connected/blob/master/bimmer_connected/vehicle.py
6583
enum VehicleType {

bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/MyBMWHandlerFactory.java

+14-3
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,19 @@
1212
*/
1313
package org.openhab.binding.mybmw.internal;
1414

15-
import static org.openhab.binding.mybmw.internal.MyBMWConstants.SUPPORTED_THING_SET;
16-
import static org.openhab.binding.mybmw.internal.MyBMWConstants.THING_TYPE_CONNECTED_DRIVE_ACCOUNT;
15+
import static org.openhab.binding.mybmw.internal.MyBMWConstants.*;
1716

1817
import org.eclipse.jdt.annotation.NonNullByDefault;
1918
import org.eclipse.jdt.annotation.Nullable;
2019
import org.openhab.binding.mybmw.internal.handler.MyBMWBridgeHandler;
2120
import org.openhab.binding.mybmw.internal.handler.MyBMWCommandOptionProvider;
2221
import org.openhab.binding.mybmw.internal.handler.VehicleHandler;
22+
import org.openhab.core.auth.client.oauth2.OAuthFactory;
2323
import org.openhab.core.i18n.LocaleProvider;
2424
import org.openhab.core.i18n.LocationProvider;
2525
import org.openhab.core.i18n.TimeZoneProvider;
2626
import org.openhab.core.io.net.http.HttpClientFactory;
27+
import org.openhab.core.net.NetworkAddressService;
2728
import org.openhab.core.thing.Bridge;
2829
import org.openhab.core.thing.Thing;
2930
import org.openhab.core.thing.ThingTypeUID;
@@ -33,6 +34,7 @@
3334
import org.osgi.service.component.annotations.Activate;
3435
import org.osgi.service.component.annotations.Component;
3536
import org.osgi.service.component.annotations.Reference;
37+
import org.osgi.service.http.HttpService;
3638

3739
/**
3840
* The {@link MyBMWHandlerFactory} is responsible for creating things and thing
@@ -45,18 +47,26 @@
4547
@Component(configurationPid = "binding.mybmw", service = ThingHandlerFactory.class)
4648
public class MyBMWHandlerFactory extends BaseThingHandlerFactory {
4749
private final HttpClientFactory httpClientFactory;
50+
private final OAuthFactory oAuthFactory;
4851
private final MyBMWCommandOptionProvider commandOptionProvider;
52+
private final NetworkAddressService networkAddressService;
53+
private final HttpService httpService;
4954
private final LocationProvider locationProvider;
5055
private final TimeZoneProvider timeZoneProvider;
5156
private final LocaleProvider localeProvider;
5257

5358
@Activate
5459
public MyBMWHandlerFactory(final @Reference HttpClientFactory httpClientFactory,
60+
final @Reference OAuthFactory oAuthFactory,
5561
final @Reference MyBMWCommandOptionProvider commandOptionProvider,
62+
final @Reference NetworkAddressService networkAddressService, final @Reference HttpService httpService,
5663
final @Reference LocaleProvider localeProvider, final @Reference LocationProvider locationProvider,
5764
final @Reference TimeZoneProvider timeZoneProvider) {
5865
this.httpClientFactory = httpClientFactory;
66+
this.oAuthFactory = oAuthFactory;
5967
this.commandOptionProvider = commandOptionProvider;
68+
this.networkAddressService = networkAddressService;
69+
this.httpService = httpService;
6070
this.locationProvider = locationProvider;
6171
this.timeZoneProvider = timeZoneProvider;
6272
this.localeProvider = localeProvider;
@@ -71,7 +81,8 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) {
7181
protected @Nullable ThingHandler createHandler(Thing thing) {
7282
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
7383
if (THING_TYPE_CONNECTED_DRIVE_ACCOUNT.equals(thingTypeUID)) {
74-
return new MyBMWBridgeHandler((Bridge) thing, httpClientFactory, localeProvider);
84+
return new MyBMWBridgeHandler((Bridge) thing, httpClientFactory, oAuthFactory, httpService,
85+
networkAddressService, localeProvider);
7586
} else if (SUPPORTED_THING_SET.contains(thingTypeUID)) {
7687
return new VehicleHandler(thing, commandOptionProvider, locationProvider, timeZoneProvider,
7788
thingTypeUID.getId());

bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/console/MyBMWCommandExtension.java

+3-5
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@
1212
*/
1313
package org.openhab.binding.mybmw.internal.console;
1414

15-
import static org.openhab.binding.mybmw.internal.MyBMWConstants.BINDING_ID;
16-
import static org.openhab.binding.mybmw.internal.MyBMWConstants.THING_TYPE_CONNECTED_DRIVE_ACCOUNT;
15+
import static org.openhab.binding.mybmw.internal.MyBMWConstants.*;
1716

1817
import java.io.File;
1918
import java.io.FileOutputStream;
@@ -253,7 +252,7 @@ private void writeJsonToFile(String pathString, String filename, String json) th
253252

254253
// ensure full path exists
255254
File file = new File(path);
256-
file.getParentFile().mkdirs();
255+
Objects.requireNonNull(file.getParentFile()).mkdirs();
257256

258257
final byte[] contents = json.getBytes(StandardCharsets.UTF_8);
259258
Files.write(file.toPath(), contents);
@@ -313,8 +312,7 @@ public boolean complete(String[] args, int cursorArgumentIndex, int cursorPositi
313312
.filter(t -> THING_TYPE_CONNECTED_DRIVE_ACCOUNT.equals(t.getThingTypeUID())
314313
&& args[1].equals(t.getConfiguration().get("userName")))
315314
.map(t -> t.getHandler()).findAny().get();
316-
List<VehicleBase> vehicles = handler != null ? handler.getMyBmwProxy().get().requestVehiclesBase()
317-
: List.of();
315+
List<VehicleBase> vehicles = handler.getMyBmwProxy().get().requestVehiclesBase();
318316
return new StringsCompleter(
319317
vehicles.stream().map(v -> v.getVin()).filter(Objects::nonNull).collect(Collectors.toList()),
320318
false).complete(args, cursorArgumentIndex, cursorPosition, candidates);

bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/discovery/VehicleDiscovery.java

+12-8
Original file line numberDiff line numberDiff line change
@@ -83,29 +83,33 @@ public void discoverVehicles() {
8383
try {
8484
return prox.requestVehicles();
8585
} catch (NetworkException e) {
86-
throw new IllegalStateException("vehicles could not be discovered: " + e.getMessage(), e);
86+
throw new IllegalStateException(e);
8787
}
8888
});
8989
vehicleList.ifPresentOrElse(vehicles -> {
90-
if (vehicles.size() > 0) {
90+
if (!vehicles.isEmpty()) {
9191
thingHandler.vehicleDiscoverySuccess();
9292
processVehicles(vehicles);
9393
} else {
94-
logger.warn("no vehicle found, maybe because of network error");
95-
thingHandler.vehicleDiscoveryError();
94+
thingHandler.vehicleDiscoveryError(MyBMWConstants.STATUS_NETWORK_ERROR);
9695
}
97-
}, () -> thingHandler.vehicleDiscoveryError());
96+
}, () -> thingHandler.vehicleDiscoveryError(Constants.EMPTY));
9897
} catch (IllegalStateException ex) {
99-
thingHandler.vehicleDiscoveryError();
98+
NetworkException ne = (NetworkException) ex.getCause();
99+
if (ne != null && (ne.getStatus() == 403 || ne.getStatus() == 429)) {
100+
thingHandler.vehicleQuotaDiscoveryError(myBMWProxy.get().getNextQuota());
101+
} else {
102+
thingHandler.vehicleDiscoveryError(MyBMWConstants.STATUS_NETWORK_ERROR);
103+
}
100104
}
101105
}
102106

103107
/**
104108
* this method is called by the bridgeHandler if the list of vehicles was retrieved successfully
105-
*
109+
*
106110
* it iterates through the list of existing things and checks if the vehicles found via the API
107111
* call are already known to OH. If not, it creates a new thing and puts it into the inbox
108-
*
112+
*
109113
* @param vehicleList
110114
*/
111115
private void processVehicles(List<Vehicle> vehicleList) {

bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/auth/ChinaAccessToken.java

-29
This file was deleted.

bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/auth/ChinaPublicKey.java

-25
This file was deleted.

bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/auth/ChinaPublicKeyResponse.java

-25
This file was deleted.

bundles/org.openhab.binding.mybmw/src/main/java/org/openhab/binding/mybmw/internal/dto/auth/ChinaTokenExpiration.java

-25
This file was deleted.

0 commit comments

Comments
 (0)