Skip to content

Commit

Permalink
Allow for captcha in North America
Browse files Browse the repository at this point in the history
  • Loading branch information
rikroe committed Oct 31, 2024
1 parent a1c80ca commit e2fa750
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 24 deletions.
7 changes: 5 additions & 2 deletions bimmer_connected/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,12 @@ class MyBMWAccount:
use_metric_units: InitVar[Optional[bool]] = None
"""Deprecated. All returned values are metric units (km, l)."""

hcaptcha_token: InitVar[Optional[str]] = None
"""Optional. Required for North America region."""

vehicles: List[MyBMWVehicle] = field(default_factory=list, init=False)

def __post_init__(self, password, log_responses, observer_position, verify, use_metric_units):
def __post_init__(self, password, log_responses, observer_position, verify, use_metric_units, hcaptcha_token):
"""Initialize the account."""

if use_metric_units is not None:
Expand All @@ -66,7 +69,7 @@ def __post_init__(self, password, log_responses, observer_position, verify, use_

if self.config is None:
self.config = MyBMWClientConfiguration(
MyBMWAuthentication(self.username, password, self.region, verify=verify),
MyBMWAuthentication(self.username, password, self.region, verify=verify, hcaptcha_token=hcaptcha_token),
log_responses=log_responses,
observer_position=observer_position,
verify=verify,
Expand Down
38 changes: 26 additions & 12 deletions bimmer_connected/api/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ def __init__(
expires_at: Optional[datetime.datetime] = None,
refresh_token: Optional[str] = None,
gcid: Optional[str] = None,
hcaptcha_token: Optional[str] = None,
verify: httpx._types.VerifyTypes = True,
):
self.username: str = username
Expand All @@ -64,6 +65,7 @@ def __init__(
self.session_id: str = str(uuid4())
self._lock: Optional[asyncio.Lock] = None
self.gcid: Optional[str] = gcid
self.hcaptcha_token: Optional[str] = hcaptcha_token
# Use external SSL context. Required in Home Assistant due to event loop blocking when httpx loads
# SSL certificates from disk. If not given, uses httpx defaults.
self.verify: Optional[httpx._types.VerifyTypes] = verify
Expand Down Expand Up @@ -183,19 +185,31 @@ async def _login_row_na(self):
"code_challenge_method": "S256",
}

authenticate_headers = {}
if self.region == Regions.NORTH_AMERICA:
if not self.hcaptcha_token:
raise MyBMWAPIError("Missing hCaptcha token for North America login")
authenticate_headers = {
"hcaptchatoken": self.hcaptcha_token,
}
# Call authenticate endpoint first time (with user/pw) and get authentication
response = await client.post(
authenticate_url,
data=dict(
oauth_base_values,
**{
"grant_type": "authorization_code",
"username": self.username,
"password": self.password,
},
),
)
authorization = httpx.URL(response.json()["redirect_to"]).params["authorization"]
try:
response = await client.post(
authenticate_url,
headers=authenticate_headers,
data=dict(
oauth_base_values,
**{
"grant_type": "authorization_code",
"username": self.username,
"password": self.password,
},
),
)
authorization = httpx.URL(response.json()["redirect_to"]).params["authorization"]
finally:
# Always reset hCaptcha token after first login attempt
self.hcaptcha_token = None

# With authorization, call authenticate endpoint second time to get code
response = await client.post(
Expand Down
6 changes: 3 additions & 3 deletions bimmer_connected/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ class Regions(str, Enum):
}

APP_VERSIONS = {
Regions.NORTH_AMERICA: "4.7.2(35379)",
Regions.REST_OF_WORLD: "4.7.2(35379)",
Regions.CHINA: "4.7.2(35379)",
Regions.NORTH_AMERICA: "4.9.2(36892)",
Regions.REST_OF_WORLD: "4.9.2(36892)",
Regions.CHINA: "4.9.2(36892)",
}

HTTPX_TIMEOUT = 30.0
Expand Down
25 changes: 18 additions & 7 deletions bimmer_connected/tests/test_account.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from bimmer_connected.api.authentication import MyBMWAuthentication, MyBMWLoginRetry
from bimmer_connected.api.client import MyBMWClient
from bimmer_connected.api.regions import get_region_from_name
from bimmer_connected.const import ATTR_CAPABILITIES, VEHICLES_URL, CarBrands
from bimmer_connected.const import ATTR_CAPABILITIES, VEHICLES_URL, CarBrands, Regions
from bimmer_connected.models import GPSPosition, MyBMWAPIError, MyBMWAuthError, MyBMWQuotaError

from . import (
Expand All @@ -32,13 +32,29 @@


@pytest.mark.asyncio
async def test_login_row_na(bmw_fixture: respx.Router):
async def test_login_row(bmw_fixture: respx.Router):
"""Test the login flow."""
account = MyBMWAccount(TEST_USERNAME, TEST_PASSWORD, get_region_from_name(TEST_REGION_STRING))
await account.get_vehicles()
assert account is not None


@pytest.mark.asyncio
async def test_login_na(bmw_fixture: respx.Router):
"""Test the login flow for North America."""
account = MyBMWAccount(TEST_USERNAME, TEST_PASSWORD, Regions.NORTH_AMERICA, hcaptcha_token="SOME_TOKEN")
await account.get_vehicles()
assert account is not None


@pytest.mark.asyncio
async def test_login_na_without_hcaptcha(bmw_fixture: respx.Router):
"""Test the login flow."""
with pytest.raises(MyBMWAPIError):
account = MyBMWAccount(TEST_USERNAME, TEST_PASSWORD, Regions.NORTH_AMERICA)
await account.get_vehicles()


@pytest.mark.asyncio
async def test_login_refresh_token_row_na_expired(bmw_fixture: respx.Router):
"""Test the login flow using refresh_token."""
Expand Down Expand Up @@ -745,8 +761,3 @@ async def test_pillow_unavailable(monkeypatch: pytest.MonkeyPatch, bmw_fixture:
await account.get_vehicles()
assert account is not None
assert len(account.vehicles) > 0

account = MyBMWAccount(TEST_USERNAME, TEST_PASSWORD, get_region_from_name("north_america"))
await account.get_vehicles()
assert account is not None
assert len(account.vehicles) > 0
39 changes: 39 additions & 0 deletions docs/source/_static/captcha_north_america.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Form with hCaptcha</title>
</head>
<body>
<p></p>
<div id="captchaResponse">
<div style="text-align: center;">
<form id="captcha_form" action="#" method="post">
<!-- hCaptcha widget -->
<div class="h-captcha" data-sitekey="dc24de9a-9844-438b-b542-60067ff4dbe9"></div><br>
<button type="submit" class="btn">Submit</button>
</form>

<!-- hCaptcha script -->
<script src="https://hcaptcha.com/1/api.js" async defer></script>
</div>
</div>
<p></p>
<script>
document.getElementById('captcha_form').addEventListener('submit', function(event) {
event.preventDefault(); // Prevent the default form submission

const hCaptchaResponse = document.querySelector('[name="h-captcha-response"]').value;
const responseElement = document.getElementById('captchaResponse');

if (hCaptchaResponse) {
content = '<div class="highlight"><pre style="word-break: break-all; white-space: pre-wrap;">'
content += hCaptchaResponse
content += '</pre></div>';
responseElement.innerHTML = content;
}
});
</script>
</body>
</html>
15 changes: 15 additions & 0 deletions docs/source/captcha.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
Captcha (North America)
=======================

Login to the :code:`north_america` region requires a captcha to be solved. Submit below form and use the returned token when creating the account object.

::

account = MyBMWAccount(USERNAME, PASSWORD, Regions.REST_OF_WORLD, hcaptcha_token=HCAPTCHA_TOKEN)

.. note::
Only the first login requires a captcha to be solved. Follow-up logins using refresh token do not require a captcha.
This requires the tokens to be stored in a file (see :ref:`cli`) or in the python object itself.

.. raw:: html
:file: _static/captcha_north_america.html
1 change: 1 addition & 0 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
:maxdepth: 2
:glob:

captcha
development/*


Expand Down

0 comments on commit e2fa750

Please sign in to comment.