6666from pyicloud_ipd .base import PyiCloudService
6767from pyicloud_ipd .exceptions import (
6868 PyiCloudFailedLoginException ,
69+ PyiCloudFailedMFAException ,
6970 PyiCloudServiceNotActivatedException ,
7071 PyiCloudServiceUnavailableException ,
7172)
@@ -169,43 +170,39 @@ def ask_password_in_console(_user: str) -> str | None:
169170
170171
171172def get_password_from_webui (
172- logger : Logger , status_exchange : StatusExchange
173- ) -> Callable [[str ], str | None ]:
174- def _intern (_user : str ) -> str | None :
175- """Request two-factor authentication through Webui."""
176- if not status_exchange .replace_status (Status .NO_INPUT_NEEDED , Status .NEED_PASSWORD ):
177- logger .error ("Expected NO_INPUT_NEEDED, but got something else" )
173+ logger : Logger , status_exchange : StatusExchange , _user : str
174+ ) -> str | None :
175+ """Request two-factor authentication through Webui."""
176+ if not status_exchange .replace_status (Status .NO_INPUT_NEEDED , Status .NEED_PASSWORD ):
177+ logger .error ("Expected NO_INPUT_NEEDED, but got something else" )
178+ return None
179+
180+ # wait for input
181+ while True :
182+ status = status_exchange .get_status ()
183+ if status == Status .NEED_PASSWORD :
184+ time .sleep (1 )
185+ else :
186+ break
187+ if status_exchange .replace_status (Status .SUPPLIED_PASSWORD , Status .CHECKING_PASSWORD ):
188+ password = status_exchange .get_payload ()
189+ if not password :
190+ logger .error ("Internal error: did not get password for SUPPLIED_PASSWORD status" )
191+ status_exchange .replace_status (
192+ Status .CHECKING_PASSWORD , Status .NO_INPUT_NEEDED
193+ ) # TODO Error
178194 return None
195+ return password
179196
180- # wait for input
181- while True :
182- status = status_exchange .get_status ()
183- if status == Status .NEED_PASSWORD :
184- time .sleep (1 )
185- else :
186- break
187- if status_exchange .replace_status (Status .SUPPLIED_PASSWORD , Status .CHECKING_PASSWORD ):
188- password = status_exchange .get_payload ()
189- if not password :
190- logger .error ("Internal error: did not get password for SUPPLIED_PASSWORD status" )
191- status_exchange .replace_status (
192- Status .CHECKING_PASSWORD , Status .NO_INPUT_NEEDED
193- ) # TODO Error
194- return None
195- return password
196-
197- return None # TODO
197+ return None # TODO
198198
199- return _intern
200199
200+ def update_password_status_in_webui (status_exchange : StatusExchange , _u : str , _p : str ) -> None :
201+ status_exchange .replace_status (Status .CHECKING_PASSWORD , Status .NO_INPUT_NEEDED )
201202
202- def update_password_status_in_webui (status_exchange : StatusExchange ) -> Callable [[str , str ], None ]:
203- def _intern (_u : str , _p : str ) -> None :
204- # TODO we are not handling wrong passwords...
205- status_exchange .replace_status (Status .CHECKING_PASSWORD , Status .NO_INPUT_NEEDED )
206- return None
207203
208- return _intern
204+ def update_auth_error_in_webui (status_exchange : StatusExchange , error : str ) -> bool :
205+ return status_exchange .set_error (error )
209206
210207
211208# def get_click_param_by_name(_name: str, _params: List[Parameter]) -> Optional[Parameter]:
@@ -234,6 +231,7 @@ def password_provider_generator(
234231) -> Dict [str , Tuple [Callable [[str ], str | None ], Callable [[str , str ], None ]]]:
235232 def _map (provider : str ) -> Tuple [Callable [[str ], str | None ], Callable [[str , str ], None ]]:
236233 if provider == "webui" :
234+ # ask_password_in_console will be replaced once we setup web
237235 return (ask_password_in_console , dummy_password_writter )
238236 if provider == "console" :
239237 return (ask_password_in_console , dummy_password_writter )
@@ -805,8 +803,8 @@ def main(
805803 if "webui" in password_providers :
806804 # replace
807805 password_providers ["webui" ] = (
808- get_password_from_webui ( logger , status_exchange ),
809- update_password_status_in_webui ( status_exchange ),
806+ partial ( get_password_from_webui , logger , status_exchange ),
807+ partial ( update_password_status_in_webui , status_exchange ),
810808 )
811809
812810 # hacky way to inject logger
@@ -1559,13 +1557,31 @@ def should_break(counter: Counter) -> bool:
15591557 except PyiCloudFailedLoginException as _error :
15601558 logger .info ("Invalid email/password combination." )
15611559 dump_responses (logger .debug , captured_responses )
1562- if "webui" in password_providers and mfa_provider == MFAProvider .WEBUI :
1563- pass
1560+ if "webui" in password_providers :
1561+ update_auth_error_in_webui (status_exchange , "Invalid email/password combination." )
1562+ continue
1563+ else :
1564+ return 1
1565+ except PyiCloudFailedMFAException as error :
1566+ logger .info (str (error ))
1567+ dump_responses (logger .debug , captured_responses )
1568+ if mfa_provider == MFAProvider .WEBUI :
1569+ update_auth_error_in_webui (status_exchange , str (error ))
1570+ continue
15641571 else :
15651572 return 1
15661573 except (PyiCloudServiceNotActivatedException , PyiCloudServiceUnavailableException ) as error :
15671574 logger .info (error )
15681575 dump_responses (logger .debug , captured_responses )
1576+ # webui will display error and wait for password again
1577+ if "webui" in password_providers or mfa_provider == MFAProvider .WEBUI :
1578+ if update_auth_error_in_webui (status_exchange , str (error )):
1579+ # retry if it was during auth
1580+ continue
1581+ else :
1582+ pass
1583+ else :
1584+ pass
15691585 # it not watching then return error
15701586 if not watch_interval :
15711587 return 1
@@ -1575,6 +1591,17 @@ def should_break(counter: Counter) -> bool:
15751591 logger .info ("Cannot connect to Apple iCloud service" )
15761592 dump_responses (logger .debug , captured_responses )
15771593 # logger.debug(error)
1594+ # webui will display error and wait for password again
1595+ if "webui" in password_providers or mfa_provider == MFAProvider .WEBUI :
1596+ if update_auth_error_in_webui (
1597+ status_exchange , "Cannot connect to Apple iCloud service"
1598+ ):
1599+ # retry if it was during auth
1600+ continue
1601+ else :
1602+ pass
1603+ else :
1604+ pass
15781605 # it not watching then return error
15791606 if not watch_interval :
15801607 return 1
0 commit comments