Skip to content

Commit

Permalink
Merge pull request #772 from flobz/fix/oauth2
Browse files Browse the repository at this point in the history
Fix/oauth2 #733
  • Loading branch information
flobz committed Feb 24, 2024
2 parents 0cdcd55 + 6ded92a commit bcd6fdc
Show file tree
Hide file tree
Showing 7 changed files with 74 additions and 21 deletions.
2 changes: 1 addition & 1 deletion psa_car_controller/psa/connected_car_api/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ def __init__(self):
# Debug file location
self.logger_file = None
# Debug switch
self.debug = False
self.debug = True

# SSL/TLS verification
# Set this to false to skip verifying SSL certificate when calling API
Expand Down
25 changes: 19 additions & 6 deletions psa_car_controller/psa/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,20 @@
PSA_CORRELATION_DATE_FORMAT = "%Y%m%d%H%M%S%f"
PSA_DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
realm_info = {
"clientsB2CPeugeot": {"oauth_url": "https://idpcvs.peugeot.com/am/oauth2/access_token", "app_name": "MyPeugeot"},
"clientsB2CCitroen": {"oauth_url": "https://idpcvs.citroen.com/am/oauth2/access_token", "app_name": "MyCitroen"},
"clientsB2CDS": {"oauth_url": "https://idpcvs.driveds.com/am/oauth2/access_token", "app_name": "MyDS"},
"clientsB2COpel": {"oauth_url": "https://idpcvs.opel.com/am/oauth2/access_token", "app_name": "MyOpel"},
"clientsB2CPeugeot": {"oauth_url": "https://idpcvs.peugeot.com/am/oauth2/access_token", "app_name": "MyPeugeot",
"scheme": "mymap"},
"clientsB2CCitroen": {"oauth_url": "https://idpcvs.citroen.com/am/oauth2/access_token", "app_name": "MyCitroen",
"scheme": "mymacsdk"},
"clientsB2CDS": {"oauth_url": "https://idpcvs.driveds.com/am/oauth2/access_token", "app_name": "MyDS",
"scheme": "mymdssdk"},

"clientsB2COpel": {"oauth_url": "https://idpcvs.opel.com/am/oauth2/access_token", "app_name": "MyOpel",
"scheme": "mymopsdk"},

"clientsB2CVauxhall": {"oauth_url": "https://idpcvs.vauxhall.co.uk/am/oauth2/access_token",
"app_name": "MyVauxhall"}
"app_name": "MyVauxhall",
"scheme": "mymvxsdk",
}
}
MQTT_BRANDCODE = {"AP": "AP",
"AC": "AC",
Expand All @@ -28,7 +36,12 @@
"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 = {"clientsB2COpel": "https://idpcvs.opel.com/am/oauth2/authorize",
"clientsB2CPeugeot": "https://idpcvs.peugeot.com/am/oauth2/authorize",
"clientsB2CCitroen": "https://idpcvs.citroen.com/am/oauth2/authorize",
"clientsB2CDS": "https://idpcvs.driveds.com/am/oauth2/authorize",
"clientsB2CVauxhall": "https://idpcvs.vauxhall.co.uk/am/oauth2/authorize"
}
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
39 changes: 36 additions & 3 deletions psa_car_controller/psa/oauth.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import logging
import hashlib
import secrets
import base64
from typing import Tuple

from http import HTTPStatus
from typing import Optional

from oauth2_client.credentials_manager import CredentialManager, ServiceInformation
from oauth2_client.credentials_manager import CredentialManager, ServiceInformation, OAuthError
from requests import Response, RequestException

from psa_car_controller.common.utils import rate_limit
Expand All @@ -22,8 +27,36 @@ def _grant_password_request_realm(self, login: str, password: str, realm: str) -
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 ValueError("Invalid length: %d" % length)
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_oauth2_redirect(self, scheme: str, country_code: str):
ret = ""
while True:
redir_uri = scheme + "://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")

logger.info("Now login to this URL in a browser: %s", url)

try:
ret = input("\nCopy+paste the resulting mymXX-code (in F12 > Network, "
"when you hit the final OK button, 36 chars, UUID format): ")
logger.info("Try getting a token with code %s", ret)
assert len(ret) == 36, "Invalid code length"
self._token_request({"grant_type": 'authorization_code', "code": ret,
"redirect_uri": redir_uri, "code_verifier": code_verifier}, False)
except (OAuthError, AssertionError):
logger.exception("Failed to get a token")
if input("Retry ? yes/NO") == "yes":
continue
break

@staticmethod
def _is_token_expired(response: Response) -> bool:
Expand Down
4 changes: 2 additions & 2 deletions psa_car_controller/psa/setup/app_decoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ def firstLaunchConfig(package_name, client_email, client_password, country_code,
# Psacc
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)
country_code, BRAND[package_name]["brand_code"])
psacc.connect()
psacc.save_config(name=config_prefix + "config.json")
res = psacc.get_vehicles()

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


class PSAClient:
def connect(self, user, password):
self.manager.init_with_user_credentials_realm(user, password, self.realm)
def connect(self):
self.manager.init_with_oauth2_redirect(realm_info[self.realm]["scheme"], self.country_code)

# pylint: disable=too-many-arguments
def __init__(self, refresh_token, client_id, client_secret, remote_refresh_token, customer_id, realm, country_code,
proxies=None, weather_api=None, abrp=None, co2_signal_api=None):
brand=None, proxies=None, weather_api=None, abrp=None, co2_signal_api=None):
self.realm = realm
self.service_information = ServiceInformation(AUTHORIZE_SERVICE,
self.service_information = ServiceInformation(AUTHORIZE_SERVICE[self.realm],
realm_info[self.realm]['oauth_url'],
client_id,
client_secret,
Expand All @@ -60,6 +60,7 @@ def __init__(self, refresh_token, client_id, client_secret, remote_refresh_token
self._record_enabled = False
self.weather_api = weather_api
self.country_code = country_code
self.brand = brand
self.info_callback = []
self.info_refresh_rate = 120
if abrp is None:
Expand Down
14 changes: 10 additions & 4 deletions psa_car_controller/web/view/config_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,10 +61,16 @@
color="secondary",
)]),
dbc.Row(dbc.Button("Submit", color="primary", id="submit-form")),
dbc.Row(
dbc.FormText(
"After submit be patient it can take some time...",
color="secondary")),
html.Div(html.P([
"1. After submit switch back to the CLI and wait for the login URL to be shown", html.Br(),
"2. Open that URL in a browser", html.Br(),
"3. Complete the login procedure there too", html.Br(),
"4. Open your browser's DevTools (F12) and then the 'Network' tab", html.Br(),
"5. Hit the final 'OK' button, under 'LOGIN SUCCESSFUL'", html.Br(),
"6. Find xxxx://oauth2redirect....?code=<copy this part>&scope=openid... "
"in the DevTools and paste it into the command line interface", html.Br(),
"7. Switch back to the browser and do the OTP config", html.Br()]
)),
dcc.Loading(
id="loading-2",
children=[html.Div([html.Div(id="form_result")])],
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ include = [

[tool.poetry.dependencies]
python = ">=3.7.2, <4.0.0"
paho-mqtt = ">=1.5.0"
paho-mqtt = ">=1.5.0, <2.0.0"
dash = ">=2.9.0, <3.0.0"
dash-daq = "^0.5.0"
plotly = ">=5"
Expand Down

0 comments on commit bcd6fdc

Please sign in to comment.