o
    l>jzi                     @  s  d Z ddlmZ ddlZddlmZ ddlmZ ddlmZm	Z	 ddl
mZ ddlmZmZ dd	lmZmZ ddlZdd
lmZ ddlmZ ddlmZmZ ddlmZ ddlmZmZmZm Z m!Z!m"Z"m#Z# ddl$m%Z% ddl&m'Z'm(Z(m)Z)m*Z* ddl+m,Z,m-Z- dZ.G dd de/Z0G dd de1eZ2eddG dd dZ3G dd dZ4dEd!d"Z5dd#dFd(d)Z6dGd,d-Z7d.d/dHd5d6Z8d.d7dId:d;Z9dJd?d@Z:dKdCdDZ;dS )Lz<Shared context and authentication helpers for the Typer CLI.    )annotationsN)	ExitStack)	dataclass)datetimetimezone)Enum)PathPurePosixPath)AnyOptional)confirm)Console)PyiCloudServiceutils)resolve_cookie_directory)PyiCloudAPIResponseExceptionPyiCloudAuthRequiredExceptionPyiCloudFailedLoginException PyiCloudNoTrustedNumberAvailablePyiCloudServiceUnavailable$PyiCloudTrustedDevicePromptException*PyiCloudTrustedDeviceVerificationException)configurable_ssl_verification   )AccountIndexEntryload_accountsprune_accountsremember_account)OutputFormat
write_jsoncommand_optionsc                   @  s   e Zd ZdZdS )CLIAbortz+Abort execution with a user-facing message.N)__name__
__module____qualname____doc__ r&   r&   I/home/thesage/.local/lib/python3.10/site-packages/pyicloud/cli/context.pyr!   )   s    r!   c                   @  s*   e Zd ZdZdZdZdZdZddd	Zd
S )LogLevelzSupported log levels.errorwarninginfodebugreturnintc                 C  s6   | t ju rtjS | t ju rtjS | t ju rtjS tjS )z Return the stdlib logging level.)r(   ERRORloggingINFODEBUGWARNINGselfr&   r&   r'   logging_level5   s   


zLogLevel.logging_levelN)r-   r.   )	r"   r#   r$   r%   r/   r3   r1   r2   r6   r&   r&   r&   r'   r(   -   s    r(   T)frozenc                   @  s   e Zd ZU dZdZded< dZded< dZded< dZd	ed
< dZ	d	ed< dZ
d	ed< dZded< dZded< dZded< dZd	ed< ejZded< ejZded< dS )CLICommandOptionsz5Command-local options captured from the leaf command.NOptional[str]usernamepasswordOptional[bool]china_mainlandTboolinteractiveFaccept_termswith_familysession_dir
http_proxyhttps_proxyno_verify_sslr(   	log_levelr   output_format)r"   r#   r$   r%   r:   __annotations__r;   r=   r?   r@   rA   rB   rC   rD   rE   r(   r3   rF   r   TEXTrG   r&   r&   r&   r'   r8   A   s   
 r8   c                   @  s~  e Zd ZdZdpddZedqddZedrddZedrddZ	dsd d!Z
dsd"d#Zdtd&d'Zdud)d*Zdvdwd,d-Zedxd/d0Zdyd2d3Zdyd4d5Zdzd7d8Zd{d9d:Zd;d<d|d@dAZd}dBdCZd}dDdEZd~dFdGZeddJdKZddMdNZdsdOdPZddQdRZddVdWZddXdYZddZd[Zdd\d]Z dd^d_Z!dd`daZ"ddbdcZ#eddfdgZ$ddhdiZ%	+dvddkdlZ&ddndoZ'd+S )CLIStatez.Shared CLI state and authenticated API access.r:   r9   r;   r=   r<   r?   r>   r@   rA   rB   rC   rD   rE   rF   r(   rG   r   r-   Nonec                C  s   |pd  | _|| _|| _|| _|| _|| _|| _|| _|	| _	|
| _
|| _|| _t | _tdd| _t | _d| _d| _| jp@d| _d| _dS )zDCapture the CLI options and shared runtime state for one invocation. T)stderrNF)stripr:   r;   r=   r?   r@   rA   rB   rC   rD   rE   rF   rG   r   consoleerr_consoler   _stack_api
_probe_api_resolved_username_logging_configured)r5   r:   r;   r=   r?   r@   rA   rB   rC   rD   rE   rF   rG   r&   r&   r'   __init__V   s&   
zCLIState.__init__optionsr8   
'CLIState'c                 C  s8   | |j |j|j|j|j|j|j|j|j|j	|j
|jdS )z0Build CLI state from one leaf command's options.r:   r;   r=   r?   r@   rA   rB   rC   rD   rE   rF   rG   rY   )clsrW   r&   r&   r'   from_options{   s   zCLIState.from_optionsc                 C  s
   t | jS )z5Return whether the user explicitly passed --username.)r>   r:   r4   r&   r&   r'   has_explicit_username   s   
zCLIState.has_explicit_usernamec                 C  s   | j tju S )z0Return whether the current command expects JSON.)rG   r   JSONr4   r&   r&   r'   json_output   s   zCLIState.json_outputc                 C  s*   | j t| j | jpd| jpdd dS )zOpen CLI-scoped resources.rL   )
verify_sslrC   rD   N)rQ   enter_contextr   rE   rC   rD   r4   r&   r&   r'   open   s   zCLIState.openc                 C  s   | j   dS )zClose CLI-scoped resources.N)rQ   closer4   r&   r&   r'   rb         zCLIState.closepayloadr
   c                 C  s   t | j| dS )zWrite a JSON payload to stdout.N)r   rO   )r5   rd   r&   r&   r'   r         zCLIState.write_jsonstrc                 C  s,   t |rt | |   dS |   dS )z0Delete a stored keyring password for a username.TF)r   password_exists_in_keyringdelete_password_in_keyringprune_local_accountsr5   r:   r&   r&   r'   delete_keyring_password   s   

z CLIState.delete_keyring_passwordNc                 C  s&   |p| j p| j }|sdS t|S )z8Return whether a keyring password exists for a username.F)rT   r:   rN   r   rg   )r5   r:   	candidater&   r&   r'   has_keyring_password   s   
zCLIState.has_keyring_passwordr   c                 C  s   t t| jS )z9Return the resolved session root for this CLI invocation.)r   r   rB   r4   r&   r&   r'   session_root   s   zCLIState.session_rootlist[AccountIndexEntry]c                 C  s   t | j| jS )z?Return discoverable local accounts after opportunistic pruning.)r   rn   rm   r4   r&   r&   r'   local_accounts   rc   zCLIState.local_accountsc                 C  s   |   S )z=Prune stale indexed accounts and return the discoverable set.)rp   r4   r&   r&   r'   ri      s   zCLIState.prune_local_accountsOptional[AccountIndexEntry]c                 C  s   t | j|S )z<Return the indexed account entry for a username, if present.)r   rn   getrj   r&   r&   r'   account_entry   re   zCLIState.account_entryc                 C  s0   | j dur| j S | |}|du rdS |dS )zHResolve China mainland mode for an account from command or stored state.Nr=   )r=   rs   rr   )r5   r:   entryr&   r&   r'   resolved_china_mainland   s   


z CLIState.resolved_china_mainlandTselectapir   rw   c                C  s8   t | j|j|jj|jj|j| jd |r|j| _dS dS )z3Persist an account entry for later local discovery.)r:   session_pathcookiejar_pathr=   keyring_hasN)	r   rn   account_namesessionry   rz   is_china_mainlandrm   rT   )r5   rx   rw   r&   r&   r'   r      s   zCLIState.remember_accountc                 C  sb   | j r| j S |  }|stdt|dkr'ddd |D }td| |d d | _ | j S )	z8Resolve the Apple ID to use for the current CLI command.z?No local accounts were found; pass --username to bootstrap one.r   
c                 s  s    | ]
}d |d  V  qdS )  - r:   Nr&   ).0rt   r&   r&   r'   	<genexpr>   s    z-CLIState._resolve_username.<locals>.<genexpr>zCMultiple local accounts were found; pass --username to choose one.
r   r:   )rT   rp   r!   lenjoin)r5   accountsrW   r&   r&   r'   _resolve_username   s    zCLIState._resolve_usernamec                 C  s   	 dS )zGReturn the default message for commands that require an active session.zdYou are not logged into any iCloud accounts. To log in, run: icloud auth login --username <apple-id>r&   r4   r&   r&   r'   not_logged_in_message  s   zCLIState.not_logged_in_messagec                 C  s   d| d| S )zKReturn the message for account-targeted commands without an active session.z#You are not logged into iCloud for $. Run: icloud auth login --username r&   rj   r&   r&   r'   !not_logged_in_for_account_message
  s   z*CLIState.not_logged_in_for_account_message	usernames	list[str]c                 C  s   d dd | D }d| S )zBReturn the message for ambiguous active-session account selection.r   c                 s  s    | ]}d | V  qdS )r   Nr&   )r   r:   r&   r&   r'   r     s    z?CLIState.multiple_logged_in_accounts_message.<locals>.<genexpr>zNMultiple logged-in iCloud accounts were found; pass --username to choose one.
)r   )r   rW   r&   r&   r'   #multiple_logged_in_accounts_message  s
   z,CLIState.multiple_logged_in_accounts_message#tuple[Optional[str], Optional[str]]c                 C  sB   | j r| j dfS t|}|r|dfS | jsdS tj|dddfS )zAReturn the password and its source for an interactive login flow.explicitkeyring)NNT)r?   prompt)r;   r   get_password_from_keyringr?   get_password)r5   r:   keyring_passwordr&   r&   r'   _password_for_login  s   

zCLIState._password_for_loginc                 C  s&   | j rdS tj| j d d| _ dS )z?Apply the requested log level once for the current CLI process.N)levelT)rU   r0   basicConfigrF   r6   r4   r&   r&   r'   _configure_logging*  s   
zCLIState._configure_loggingc                 C  s   | j r| j S t|S )zEReturn a non-interactive password for service-level reauthentication.)r;   r   r   rj   r&   r&   r'   _stored_password_for_session1  s   
z%CLIState._stored_password_for_sessionr   countr.   c              
   C  sl   |dks| j s	dS tj|dd}zt|}W n ty' } ztd|d}~ww |dk s0||kr4td|S )zDPrompt for a zero-based selection index when multiple choices exist.r   r   0)defaultzInvalid device selection.N)r?   typerr   r.   
ValueErrorr!   )r5   r   r   rawidxexcr&   r&   r'   _prompt_index8  s   
zCLIState._prompt_indexc                 C  s  t t|dg pg }|rP| jd t|D ]\}}| jd| d|  q| dt|}| jd z	|||  W n tyO } zt	d|d}~ww | j
sWt	d	z3| s`t	d
t|dd}|rn| j| t|dd}|dkr| jd n
|dkr| jd W n1 ty } zt	d|d}~w ty } zt	d|d}~w ty } zt	d|d}~ww d}	t|	D ]<}
td}z||}W n ty } zt	d|d}~ww |r n|	|
 d }|dkrt	d| jd| d q|js	|  dS dS )zHComplete Apple's HSA2 flow using a security key or code-based challenge.fido2_devicesz#Security key verification required.  z: Security key zSelect security key indexz,Touch the selected security key to continue.z!Security key verification failed.NzLTwo-factor authentication is required, but interactive prompts are disabled.zBThis 2FA challenge requires a security key. Connect one and retry.two_factor_delivery_noticetwo_factor_delivery_methodunknowntrusted_devicez5Requested a 2FA prompt on your trusted Apple devices.smszRequested a 2FA code by SMS.zQTwo-factor authentication requires a trusted phone number, but none was returned.z0Failed to request the 2FA trusted-device prompt.z#Failed to request the 2FA SMS code.   zEnter 2FA codez-Failed to verify the 2FA trusted-device code.r   r   zFailed to verify the 2FA code.zInvalid 2FA code. z attempt(s) remaining.)listgetattrrO   print	enumerater   r   confirm_security_key	Exceptionr!   r?   request_2fa_coder   r   r   ranger   r   validate_2fa_coder   is_trusted_sessiontrust_session)r5   rx   r   index_deviceselected_indexr   noticedelivery_methodmax_attemptsattemptcodeis_validremaining_attemptsr&   r&   r'   _handle_2faE  s   



zCLIState._handle_2fac                 C  s   t |jpg }|std| jd t|D ]\}}|dr"dnd}| jd| d|  q| dt|}|| }| j	sDtd	|
|sMtd
td}|||s\tddS )z5Complete Apple's legacy two-step authentication flow.zITwo-step authentication is required but no trusted devices are available.zTrusted devices:phoneNumberzSMS trusted devicezTrusted devicer   z: zSelect trusted device indexzJTwo-step authentication is required, but interactive prompts are disabled.z)Failed to send the 2SA verification code.zEnter 2SA verification codezFailed to verify the 2SA code.N)r   trusted_devicesr!   rO   r   r   rr   r   r   r?   send_verification_coder   r   validate_verification_code)r5   rx   devicesr   devicelabelr   r   r&   r&   r'   _handle_2sa  s,   

zCLIState._handle_2sac              
   C  s  | j dur| j S |  }| |\}}|std|   zt||| || j| j| j	d}W n& t
yU } z|dkrIt|rIt| |   td| |d}~ww t|sh| jrhtdrht|| |jrq| | n|jry| | || _ | | |S )z8Return a PyiCloudService, bootstrapping login if needed.Nz6No password supplied and no stored password was found.)apple_idr;   r=   cookie_directoryr@   rA   r   zBad username or password for zSave password in keyring?)rR   r   r   r!   r   r   ru   rB   r@   rA   r   r   rg   rh   ri   r?   r   store_password_in_keyringrequires_2far   requires_2sar   r   )r5   r:   r;   password_sourcerx   errr&   r&   r'   get_login_api  sN   


	

zCLIState.get_login_apic                 C  s"  | j dur| j S | jrG|  }| |}| }|d s#t| || |}| ||s=| }|d s=t| ||| _ | 	| |S | 
 }|sSt|  t|dkret| dd |D |d \}}| |j}| ||s| }|d st| |j|| _ | 	| |S )zDReturn an authenticated PyiCloudService backed by an active session.Nauthenticatedr   c                 S  s   g | ]\}}|j qS r&   r|   )r   rx   _statusr&   r&   r'   
<listcomp>  s    z$CLIState.get_api.<locals>.<listcomp>r   )rR   r\   r   build_probe_apiget_auth_statusr!   r   build_session_api_hydrate_api_from_prober   active_session_probesr   r   r   r|   )r5   r:   	probe_apistatusrx   active_probesr   r&   r&   r'   get_api  sH   





zCLIState.get_apic              	   C  s,   |    t|| j| || j| j| jddS )z>Build a non-authenticating PyiCloudService for session probes.Fr   r;   r=   r   r@   rA   authenticate)r   r   r;   ru   rB   r@   rA   rj   r&   r&   r'   r     s   zCLIState.build_probe_apic              	   C  s0   |    t|| || || j| j| jddS )zEBuild a session-backed API that can satisfy service reauthentication.Fr   )r   r   r   ru   rB   r@   rA   rj   r&   r&   r'   r   	  s   zCLIState.build_session_apir   Optional[PyiCloudService]c                 C  s   |du rdS t |dd}t|tr|sdS t|| _t | dd}|d}t|tr<t|tr<d|v r<|d|d i d|v rHt| d|d  d	S )
zAPopulate auth-derived state on a session-backed API from a probe.NFdataparamsdsInfodsidwebservices_webservicesT)r   
isinstancedictr   rr   updatesetattr)rx   r   
probe_datar   ds_infor&   r&   r'   r     s   

z CLIState._hydrate_api_from_probec                 C  s*   | j dur| j S |  }| || _ | j S )zFReturn a cached non-authenticating PyiCloudService for session probes.N)rS   r   r   rj   r&   r&   r'   get_probe_api0  s
   
zCLIState.get_probe_apidict[str, Any]c                 C  sB   |p|   }t|jj}t|jj}t|t|| | dS )z0Return session storage paths and presence flags.)ry   rz   has_session_filehas_cookiejar_file)r   r   r}   ry   rz   rf   exists)r5   rx   r   ry   rz   r&   r&   r'   auth_storage_info9  s   zCLIState.auth_storage_info,list[tuple[PyiCloudService, dict[str, Any]]]c                 C  sz   g }|   D ]4}t|d }t|d }| s| sq| |d }| }|d r:| j|dd |||f q|S )zDReturn authenticated sessions discoverable from local session files.ry   rz   r:   r   Frv   )rp   r   r   r   r   r   append)r5   probesrt   ry   rz   rx   r   r&   r&   r'   r   H  s   zCLIState.active_session_probes)r:   r9   r;   r9   r=   r<   r?   r>   r@   r>   rA   r>   rB   r9   rC   r9   rD   r9   rE   r>   rF   r(   rG   r   r-   rK   )rW   r8   r-   rX   )r-   r>   )r-   rK   )rd   r
   r-   rK   )r:   rf   r-   r>   N)r:   r9   r-   r>   )r-   r   )r-   ro   )r:   rf   r-   rq   )r:   rf   r-   r<   )rx   r   rw   r>   r-   rK   )r-   rf   )r:   rf   r-   rf   )r   r   r-   rf   )r:   rf   r-   r   )r:   rf   r-   r9   )r   rf   r   r.   r-   r.   )rx   r   r-   rK   )r-   r   )r:   rf   r-   r   )rx   r   r   r   r-   r>   )rx   r   r-   r   )r-   r   )(r"   r#   r$   r%   rV   classmethodr[   propertyr\   r^   ra   rb   r   rk   rm   rn   rp   ri   rs   ru   r   r   r   r   staticmethodr   r   r   r   r   r   r   r   r   r   r   r   r   r   r   r&   r&   r&   r'   rJ   S   sT    
%













	




D

.
+


rJ   ctxtyper.Contextr-   c                 C  sb   |   }|j}t|tr|S tdi | jti }t|}|	  |
|j ||_|| _|S )z1Return the resolved CLI state for a leaf command.Nr&   )	find_rootobjr   rJ   r8   metarr   COMMAND_OPTIONS_META_KEYr[   ra   call_on_closerb   )r   root_ctxstaterW   resolvedr&   r&   r'   	get_stateY  s   

r  r   r   rf   r|   r9   c             
   C  s~   z| W S  t y } z
t|  d| |d}~w ttfy> } z|r2t|  d| d| |t|  d|d}~ww )zBWrap a service call with user-facing service-unavailable handling.z service unavailable: Nz  requires re-authentication for r   z4 requires re-authentication. Run: icloud auth login.)r   r!   r   r   )r   fnr|   r   r&   r&   r'   service_callj  s*   r  valueOptional[datetime]c              
   C  s~   | du rdS |   }|dr|dd d }zt|}W n ty0 } ztd|d}~ww |jdu r=|jt	j
d}|S )z"Parse an ISO-8601 datetime string.NZz+00:00z$Expected an ISO-8601 datetime value.)tzinfo)rN   endswithr   fromisoformatr   r   BadParameterr
  replacer   utc)r  
normalizeddtr   r&   r&   r'   parse_datetime|  s   

r  F)require_uniquerx   r   queryr  r>   c                  s8  |   ttd fdd jd}|D ]}tt|dd   }|r.|kr.|  S qg }t }|D ];}tt|dd  }t|ddt|ddg}tfd	d
|D sZq6|patt	|}	|	|v rgq6|
|	 || q6|s|td| d|rt|dkrddd
 |D }
td| d|
 |d S )z6Return a device matched by id or common display names.zFind Myc                     s    j S r   )r   r&   )rx   r&   r'   <lambda>  s    z resolve_device.<locals>.<lambda>r   idrL   namedeviceDisplayNamec                 3  s$    | ]}t |   kV  qd S r   )rf   rN   lower)r   rl   )loweredr&   r'   r     s    
z!resolve_device.<locals>.<genexpr>zNo device matched 'z'.r   r   c              
   s  s@    | ]}d t |dd dt |dd dt |dd dV  qdS )	r   r  rL   z (r  z / r  )N)r   )r   r   r&   r&   r'   r     s    



zMultiple devices matched 'z'. Use a device id instead.
r   )rN   r  r   r  r|   rf   r   setanyr  addr   r!   r   r   )rx   r  r  r   r   
identifiermatchesseen_ids
candidates
dedupe_keyrW   r&   )rx   r  r'   resolve_device  sF   



r$  )trashpathr%  c                C  sN   |r| j n| j}t|pd}t|dv r|S |jD ]}|dv r q|| }q|S )zResolve an iCloud Drive node./>   .r'  >   rL   r'  )r%  rootr	   rf   parts)driver&  r%  noder  partr&   r&   r'   resolve_drive_node  s   

r.  responser
   rK   c                 C  sn   t | dr| jddD ]	}|r|| qdS t | dr3t | jdr5	 | jd}|s-dS || q#dS dS )z;Write a download response to a file, streaming if possible.iter_contenti    )
chunk_sizeNr   read)hasattrr0  writer   r2  )r/  file_outchunkr&   r&   r'   _write_to_file  s   


r7  outputr   c                 C  sv   t | dpt | dot | jd}|std|jjddd |d}t| | W d   dS 1 s4w   Y  dS )	z#Stream a download response to disk.r0  r   r2  z,The download response could not be streamed.T)parentsexist_okwbN)r3  r   r!   parentmkdirra   r7  )r/  r8  
can_streamr5  r&   r&   r'   write_response_to_path  s   
"r?  )r   r   r-   rJ   )r   rf   r|   r9   )r  r9   r-   r  )rx   r   r  rf   r  r>   )r&  rf   r%  r>   )r/  r
   r-   rK   )r/  r
   r8  r   r-   rK   )<r%   
__future__r   r0   
contextlibr   dataclassesr   r   r   enumr   pathlibr   r	   typingr
   r   r   clickr   rich.consoler   pyicloudr   r   pyicloud.baser   pyicloud.exceptionsr   r   r   r   r   r   r   pyicloud.ssl_contextr   account_indexr   r   r   r   r8  r   r   r   RuntimeErrorr!   rf   r(   r8   rJ   r  r  r  r$  r.  r7  r?  r&   r&   r&   r'   <module>   sF    $	    


.
