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

Accomodate for Stellantis' Jan 23 OAuth changes #754

Merged
merged 13 commits into from
Feb 19, 2024
2 changes: 1 addition & 1 deletion psa_car_controller/psa/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"program3": {"day": [0, 0, 0, 0, 0, 0, 0], "hour": 34, "minute": 7, "on": 0},
"program4": {"day": [0, 0, 0, 0, 0, 0, 0], "hour": 34, "minute": 7, "on": 0}
}
AUTHORIZE_SERVICE = "https://api.mpsa.com/api/connectedcar/v2/oauth/authorize"
AUTHORIZE_SERVICE = "https://idpcvs.opel.com/am/oauth2/authorize"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Has this change been tested for other brands?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For lack of test accounts: no. Stellantis seems to be doing all kinds of crazy OAuth proxying internally so maybe if we're lucky the endpoints can just be used interchangeably. I found the original URL from the Android app's logcat output, the others I just found them in classes4.dex so I just made AUTHORIZE_SERVICE a dict too.

classes4.dex.txt:43f3a0: 1a02 bd90 |0012: const-string v2, "https://idpcvs.driveds.com/am/oauth2/authorize" // string@90bd
classes4.dex.txt:43f3ec: 1a02 c390 |0038: const-string v2, "https://idpcvs.peugeot.com/am/oauth2/authorize" // string@90c3
classes4.dex.txt:43f438: 1a02 ba90 |005e: const-string v2, "https://idpcvs.citroen.com/am/oauth2/authorize" // string@90ba
classes4.dex.txt:43f488: 1a02 c090 |0086: const-string v2, "https://idpcvs.opel.com/am/oauth2/authorize" // string@90c0
classes4.dex.txt:43f4da: 1a02 c690 |00af: const-string v2, "https://idpcvs.vauxhall.co.uk/am/oauth2/authorize" // string@90c6

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes you are right.
Now I get peugeot link, but it still shows this error while trying to access
{ "error": "redirect_uri_mismatch", "error_description": "The redirection URI provided does not match a pre-registered value." }

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oha. The "op" in "mymop" stands for Opel then I guess 😅 🤦
So let's pass the brand_code into the URL too...

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oha. The "op" in "mymop" stands for Opel then I guess 😅 🤦 So let's pass the brand_code into the URL too...

This worked for me, Thanks :-)

However, I have no idea how to incorporate this in to HA

REMOTE_URL = "https://api.groupe-psa.com/connectedcar/v4/virtualkey/remoteaccess/token?client_id="
BRAND = {"com.psa.mym.myopel": {"realm": "clientsB2COpel", "brand_code": "OP", "app_name": "MyOpel"},
"com.psa.mym.mypeugeot": {"realm": "clientsB2CPeugeot", "brand_code": "AP", "app_name": "MyPeugeot"},
Expand Down
26 changes: 24 additions & 2 deletions psa_car_controller/psa/oauth.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import logging
import hashlib
import secrets
import base64
from typing import Tuple

from http import HTTPStatus
from typing import Optional

Expand All @@ -22,8 +27,25 @@
return {"grant_type": 'password', "username": login, "scope": ' '.join(self.service_information.scopes),
"password": password, "realm": realm}

def init_with_user_credentials_realm(self, login: str, password: str, realm: str):
self._token_request(self._grant_password_request_realm(login, password, realm), True)
def generate_sha256_pkce(self, length: int) -> Tuple[str, str]:
if not (43 <= length <= 128):
raise Exception("Invalid length: " % str(length))

Check notice on line 32 in psa_car_controller/psa/oauth.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

psa_car_controller/psa/oauth.py#L32

Raising too general exception: Exception (broad-exception-raised)
verifier = secrets.token_urlsafe(length)
encoded = base64.urlsafe_b64encode(hashlib.sha256(verifier.encode('ascii')).digest())
challenge = encoded.decode('ascii')[:-1]
return verifier, challenge

def init_with_user_credentials_realm(self, country_code: str, realm: str):

Check notice on line 38 in psa_car_controller/psa/oauth.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

psa_car_controller/psa/oauth.py#L38

Unused argument 'realm' (unused-argument)
redir_uri = "mymop://oauth2redirect/" + country_code.lower()
code_verifier, code_challenge = self.generate_sha256_pkce(64)
url = self.generate_authorize_url(redir_uri, secrets.token_urlsafe(16), code_challenge=code_challenge, code_challenge_method="S256")

Check notice on line 41 in psa_car_controller/psa/oauth.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

psa_car_controller/psa/oauth.py#L41

line too long (140 > 120 characters) (E501)

ret = ""
while len(ret) != 36:
logger.info("Now login to this URL in a browser: " + url)

Check notice on line 45 in psa_car_controller/psa/oauth.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

psa_car_controller/psa/oauth.py#L45

Use lazy % formatting in logging functions (logging-not-lazy)
ret = input("\nCopy+paste the resulting mymop-code (in F12 > Network when you hit the final OK button, 36 chars, UUID format): ")

Check notice on line 46 in psa_car_controller/psa/oauth.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

psa_car_controller/psa/oauth.py#L46

line too long (141 > 120 characters) (E501)

self._token_request(dict(grant_type='authorization_code', code=ret, redirect_uri=redir_uri, code_verifier=code_verifier), False)

Check notice on line 48 in psa_car_controller/psa/oauth.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

psa_car_controller/psa/oauth.py#L48

Consider using '{"grant_type": 'authorization_code', "code": ret, "redirect_uri": redir_uri, ... }' instead of a call to 'dict'. (use-dict-literal)

Check notice on line 48 in psa_car_controller/psa/oauth.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

psa_car_controller/psa/oauth.py#L48

line too long (136 > 120 characters) (E501)

@staticmethod
def _is_token_expired(response: Response) -> bool:
Expand Down
2 changes: 1 addition & 1 deletion psa_car_controller/psa/setup/app_decoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ def firstLaunchConfig(package_name, client_email, client_password, country_code,
psacc = PSAClient(None, apk_parser.client_id, apk_parser.client_secret,
None, customer_id, BRAND[package_name]["realm"],
country_code)
psacc.connect(client_email, client_password)
psacc.connect(country_code)
psacc.save_config(name=config_prefix + "config.json")
res = psacc.get_vehicles()

Expand Down
4 changes: 2 additions & 2 deletions psa_car_controller/psacc/application/psa_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@


class PSAClient:
def connect(self, user, password):
self.manager.init_with_user_credentials_realm(user, password, self.realm)
def connect(self, country_code):
self.manager.init_with_user_credentials_realm(country_code, self.realm)

# pylint: disable=too-many-arguments
def __init__(self, refresh_token, client_id, client_secret, remote_refresh_token, customer_id, realm, country_code,
Expand Down