Skip to content

Commit cb70c59

Browse files
Add password mode
Change requested by Serge Droz
1 parent f9e08ee commit cb70c59

File tree

1 file changed

+54
-49
lines changed

1 file changed

+54
-49
lines changed

proton/api.py

+54-49
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class Session:
4343
>>> import proton
4444
>>> s = proton.Session("https://url-to-api.ch")
4545
>>> s.enable_alternative_routing = True
46-
>>> s.api_request('/api/endpoint')
46+
>>> s.api_request("/api/endpoint")
4747
<Response [200]>
4848
"""
4949
_base_headers = {
@@ -73,10 +73,10 @@ def load(
7373
Returns:
7474
proton.Session
7575
"""
76-
api_url = dump['api_url']
77-
appversion = dump['appversion']
78-
user_agent = dump['User-Agent']
79-
cookies = dump.get('cookies', {})
76+
api_url = dump["api_url"]
77+
appversion = dump["appversion"]
78+
user_agent = dump["User-Agent"]
79+
cookies = dump.get("cookies", {})
8080
s = Session(
8181
api_url=api_url,
8282
log_dir_path=log_dir_path,
@@ -88,10 +88,10 @@ def load(
8888
proxies=proxies
8989
)
9090
requests.utils.add_dict_to_cookiejar(s.s.cookies, cookies)
91-
s._session_data = dump['session_data']
91+
s._session_data = dump["session_data"]
9292
if s.UID is not None:
93-
s.s.headers['x-pm-uid'] = s.UID
94-
s.s.headers['Authorization'] = 'Bearer ' + s.AccessToken
93+
s.s.headers["x-pm-uid"] = s.UID
94+
s.s.headers["Authorization"] = "Bearer " + s.AccessToken
9595
return s
9696

9797
def dump(self):
@@ -104,11 +104,11 @@ def dump(self):
104104
dict
105105
"""
106106
return {
107-
'api_url': self.__api_url,
108-
'appversion': self.__appversion,
109-
'User-Agent': self.__user_agent,
110-
'cookies': self.s.cookies.get_dict(),
111-
'session_data': self._session_data
107+
"api_url": self.__api_url,
108+
"appversion": self.__appversion,
109+
"User-Agent": self.__user_agent,
110+
"cookies": self.s.cookies.get_dict(),
111+
"session_data": self._session_data
112112
}
113113

114114
def __init__(
@@ -165,8 +165,8 @@ def __init__(
165165
if self.__tls_pinning_enabled:
166166
self.s.mount(self.__api_url, TLSPinningAdapter())
167167

168-
self.s.headers['x-pm-appversion'] = appversion
169-
self.s.headers['User-Agent'] = user_agent
168+
self.s.headers["x-pm-appversion"] = appversion
169+
self.s.headers["User-Agent"] = user_agent
170170

171171
def api_request(
172172
self, endpoint,
@@ -205,11 +205,11 @@ def api_request(
205205
fct = self.s.post
206206
else:
207207
fct = {
208-
'get': self.s.get,
209-
'post': self.s.post,
210-
'put': self.s.put,
211-
'delete': self.s.delete,
212-
'patch': self.s.patch
208+
"get": self.s.get,
209+
"post": self.s.post,
210+
"put": self.s.put,
211+
"delete": self.s.delete,
212+
"patch": self.s.patch
213213
}.get(method.lower())
214214

215215
if fct is None:
@@ -273,8 +273,8 @@ def api_request(
273273
}
274274
)
275275

276-
if response['Code'] not in [1000, 1001]:
277-
if response['Code'] == 9001:
276+
if response["Code"] not in [1000, 1001]:
277+
if response["Code"] == 9001:
278278
self.__captcha_token = response["Details"]["HumanVerificationToken"]
279279

280280
raise ProtonAPIError(response)
@@ -344,7 +344,7 @@ def verify_modulus(self, armored_modulus):
344344
verified = self.__gnupg.decrypt(armored_modulus)
345345

346346
if not (verified.valid and verified.fingerprint.lower() == SRP_MODULUS_KEY_FINGERPRINT):
347-
raise ValueError('Invalid modulus')
347+
raise ValueError("Invalid modulus")
348348

349349
return base64.b64decode(verified.data.strip())
350350

@@ -365,7 +365,7 @@ def authenticate(self, username, password, human_verification=None):
365365

366366
payload = {"Username": username}
367367
if self.__clientsecret:
368-
payload['ClientSecret'] = self.__clientsecret
368+
payload["ClientSecret"] = self.__clientsecret
369369

370370
additional_headers = {}
371371

@@ -381,7 +381,7 @@ def authenticate(self, username, password, human_verification=None):
381381
additional_headers=additional_headers
382382
)
383383

384-
modulus = self.verify_modulus(info_response['Modulus'])
384+
modulus = self.verify_modulus(info_response["Modulus"])
385385
server_challenge = base64.b64decode(info_response["ServerEphemeral"])
386386
salt = base64.b64decode(info_response["Salt"])
387387
version = info_response["Version"]
@@ -391,38 +391,39 @@ def authenticate(self, username, password, human_verification=None):
391391
client_proof = usr.process_challenge(salt, server_challenge, version)
392392

393393
if client_proof is None:
394-
raise ValueError('Invalid challenge')
394+
raise ValueError("Invalid challenge")
395395

396396
# Send response
397397
payload = {
398398
"Username": username,
399399
"ClientEphemeral": base64.b64encode(client_challenge).decode(
400-
'utf8'
400+
"utf8"
401401
),
402-
"ClientProof": base64.b64encode(client_proof).decode('utf8'),
402+
"ClientProof": base64.b64encode(client_proof).decode("utf8"),
403403
"SRPSession": info_response["SRPSession"],
404404
}
405405
if self.__clientsecret:
406-
payload['ClientSecret'] = self.__clientsecret
406+
payload["ClientSecret"] = self.__clientsecret
407407
auth_response = self.api_request("/auth", payload)
408408

409409
if "ServerProof" not in auth_response:
410410
raise ValueError("Invalid password")
411411

412412
usr.verify_session(base64.b64decode(auth_response["ServerProof"]))
413413
if not usr.authenticated():
414-
raise ValueError('Invalid server proof')
414+
raise ValueError("Invalid server proof")
415415

416416
self._session_data = {
417-
'UID': auth_response["UID"],
418-
'AccessToken': auth_response["AccessToken"],
419-
'RefreshToken': auth_response["RefreshToken"],
420-
'Scope': auth_response["Scope"].split(),
417+
"UID": auth_response["UID"],
418+
"AccessToken": auth_response["AccessToken"],
419+
"RefreshToken": auth_response["RefreshToken"],
420+
"PasswordMode": auth_response["PasswordMode"],
421+
"Scope": auth_response["Scope"].split(),
421422
}
422423

423424
if self.UID is not None:
424-
self.s.headers['x-pm-uid'] = self.UID
425-
self.s.headers['Authorization'] = 'Bearer ' + self.AccessToken
425+
self.s.headers["x-pm-uid"] = self.UID
426+
self.s.headers["Authorization"] = "Bearer " + self.AccessToken
426427

427428
return self.Scope
428429

@@ -438,17 +439,17 @@ def provide_2fa(self, code):
438439
The returning dict contains the Scope of the account. This allows
439440
to identify if the account is locked, has unpaid invoices, etc.
440441
"""
441-
ret = self.api_request('/auth/2fa', {"TwoFactorCode": code})
442-
self._session_data['Scope'] = ret['Scope']
442+
ret = self.api_request("/auth/2fa", {"TwoFactorCode": code})
443+
self._session_data["Scope"] = ret["Scope"]
443444

444445
return self.Scope
445446

446447
def logout(self):
447448
"""Logout from API."""
448449
if self._session_data:
449-
self.api_request('/auth', method='DELETE')
450-
del self.s.headers['Authorization']
451-
del self.s.headers['x-pm-uid']
450+
self.api_request("/auth", method="DELETE")
451+
del self.s.headers["Authorization"]
452+
del self.s.headers["x-pm-uid"]
452453
self._session_data = {}
453454

454455
def refresh(self):
@@ -459,17 +460,17 @@ def refresh(self):
459460
re-authenticate.
460461
"""
461462
refresh_response = self.api_request(
462-
'/auth/refresh',
463+
"/auth/refresh",
463464
{
464465
"ResponseType": "token",
465466
"GrantType": "refresh_token",
466467
"RefreshToken": self.RefreshToken,
467468
"RedirectURI": "http://protonmail.ch"
468469
}
469470
)
470-
self._session_data['AccessToken'] = refresh_response["AccessToken"]
471-
self._session_data['RefreshToken'] = refresh_response["RefreshToken"]
472-
self.s.headers['Authorization'] = 'Bearer ' + self.AccessToken
471+
self._session_data["AccessToken"] = refresh_response["AccessToken"]
472+
self._session_data["RefreshToken"] = refresh_response["RefreshToken"]
473+
self.s.headers["Authorization"] = "Bearer " + self.AccessToken
473474

474475
def get_alternative_routes_from_dns(self, callback=None):
475476
"""Get alternative routes to circumvent firewalls and API restrictions.
@@ -642,16 +643,20 @@ def force_skip_alternative_routing(self, newvalue):
642643

643644
@property
644645
def UID(self):
645-
return self._session_data.get('UID', None)
646+
return self._session_data.get("UID", None)
646647

647648
@property
648649
def AccessToken(self):
649-
return self._session_data.get('AccessToken', None)
650+
return self._session_data.get("AccessToken", None)
650651

651652
@property
652653
def RefreshToken(self):
653-
return self._session_data.get('RefreshToken', None)
654+
return self._session_data.get("RefreshToken", None)
655+
656+
@property
657+
def PasswordMode(self):
658+
return self._session_data.get("PasswordMode", None)
654659

655660
@property
656661
def Scope(self):
657-
return self._session_data.get('Scope', [])
662+
return self._session_data.get("Scope", [])

0 commit comments

Comments
 (0)