o
    ΔaZy                     @   s  d dl mZ 	 eZg dZzd dlmZ W n ey$   d dlmZ Y nw d dl	Z	d dl
Z
d dlZd dlmZ d dlZd dlmZ d dlZzd dlmZ W n ey\   d dlmZ Y nw zd dlmZ W n eyt   d dlmZ Y nw d dlZd d	lmZmZ d d
lmZ eeu reZneZd dl m!Z! d dl"m#Z$m%Z%m&Z&m'Z' d dl(m)Z) dZ*dZ+dZ,dZ-dZ.e/e0e1fZ2dd Z3dd Z4G dd de&Z5G dd de$Z#G dd de$Z6G dd de7Z8G dd  d e8Z9G d!d" d"e8Z:G d#d$ d$e8Z;G d%d& d&e7Z<G d'd( d(e<Z=G d)d* d*e=Z>G d+d, d,e?Z@G d-d. d.e@ZAG d/d0 d0e@ZBG d1d2 d2eBZCG d3d4 d4eBZDG d5d6 d6eDZEG d7d8 d8e@ZFG d9d: d:e@ZGG d;d< d<e@ZHG d=d> d>e@ZIdS )?    )print_function)AccessTokenAnonymousAccessToken AuthorizeRequestTokenWithBrowserCredentialStoreRequestTokenAuthorizationEngineConsumerCredentials)StringION)select)stdin)	urlencode)urljoin)	b64decode	b64encode)parse_qs)	HTTPError)r   r   OAuthAuthorizerSystemWideConsumer)urisz+request-tokenz+access-tokenz+authorize-token   i  c                   C   s   t tjddS )zWhether the user has disabled SSL certificate connection.

    Some testing servers have broken certificates.  Rather than raising an
    error, we allow an environment variable,
    ``LP_DISABLE_SSL_CERTIFICATE_VALIDATION`` to disable the check.
    Z%LP_DISABLE_SSL_CERTIFICATE_VALIDATIONF)boolosenvironget r   r   :/usr/lib/python3/dist-packages/launchpadlib/credentials.py$_ssl_certificate_validation_disabledV   s   
r   c                 C   sD   t  }tj|dj| d|t|d\}}|jdkrt||||fS )zPOST to ``url`` with ``headers`` and a body of urlencoded ``params``.

    Wraps it up to make sure we avoid the SSL certificate validation if our
    environment tells us to.  Also, raises an error on non-200 statuses.
    )Z"disable_ssl_certificate_validationZPOST)methodheadersbody   )r   httplib2ZHttpZrequestr   statusr   )urlr   paramsZcert_disabledresponsecontentr   r   r   
_http_postc   s   


r(   c                   @   sX   e Zd ZdZdZdZdZdZdZdd Z	e
d	d
 ZdejefddZejfddZdS )r	   zStandard credentials storage and usage class.

    :ivar consumer: The consumer (application)
    :type consumer: `Consumer`
    :ivar access_token: Access information on behalf of the user
    :type access_token: `AccessToken`
    NZuridictz<BR>
c                 C   s0   t  }| | | }t|tr|d}|S )zeTurn this object into a string.

        This should probably be moved into OAuthAuthorizer.
        utf-8)r
   savegetvalue
isinstanceunicode_typeencode)selfZsio
serializedr   r   r   	serialize   s   


zCredentials.serializec                 C   s,   |  }t |ts|d}|t| |S )z}Create a `Credentials` object from a serialized string.

        This should probably be moved into OAuthAuthorizer.
        r+   )r.   r/   decodeloadr
   )clsvaluecredentialsr   r   r   from_string   s
   

zCredentials.from_stringc           	      C   s   | j dus	J d| jdu sJ dt|}t| j jddd}|t }d|i}|| jkr1d|d	< t|||\}}t	|t
rC|d
}|| jkr]t|}|durU||d< t|| _|S t|| _d|t| jjf }|durz|| j_|d| 7 }|S )a  Request an OAuth token to Launchpad.

        Also store the token in self._request_token.

        This method must not be called on an object with no consumer
        specified or if an access token has already been obtained.

        :param context: The context of this token, that is, its scope of
            validity within Launchpad.
        :param web_root: The URL of the website on which the token
            should be requested.
        :token_format: How the token should be
            presented. URI_TOKEN_FORMAT means just return the URL to
            the page that authorizes the token.  DICT_TOKEN_FORMAT
            means return a dictionary describing the token
            and the site's authentication policy.

        :return: If token_format is URI_TOKEN_FORMAT, the URL for the
            user to authorize the `AccessToken` provided by
            Launchpad. If token_format is DICT_TOKEN_FORMAT, a dict of
            information about the new access token.
        NzConsumer not specified.zAccess token already obtained.	PLAINTEXT&)oauth_consumer_keyoauth_signature_methodoauth_signatureRefererzapplication/jsonZAcceptr+   
lp.contextz%s%s?oauth_token=%sz&lp.context=%s)consumeraccess_tokenr   lookup_web_rootr)   keyrequest_token_pageDICT_TOKEN_FORMATr(   r.   bytesr4   jsonloadsr   from_params_request_tokenr9   authorize_token_pagecontext)	r1   rM   web_roottoken_formatr%   r$   r   r&   r'   r   r   r   get_request_token   s>   





zCredentials.get_request_tokenc                 C   sl   | j dus	J dt|}t| jjd| j jd| j j d}|t }d|i}t|||\}}t	
|| _dS )ad  Exchange the previously obtained request token for an access token.

        This method must not be called unless get_request_token() has been
        called and completed successfully.

        The access token will be stored as self.access_token.

        :param web_root: The base URL of the website that granted the
            request token.
        Nz5get_request_token() doesn't seem to have been called.r:   z&%s)r<   r=   oauth_tokenr>   r?   )rK   r   rC   r)   rA   rD   secretaccess_token_pager(   r   r9   rB   )r1   rN   r%   r$   r   r&   r'   r   r   r   'exchange_request_token_for_access_token   s   

z3Credentials.exchange_request_token_for_access_token)__name__
__module____qualname____doc__rK   ZURI_TOKEN_FORMATrF   ZITEM_SEPARATORNEWLINEr3   classmethodr9   r   ZSTAGING_WEB_ROOTrP   rT   r   r   r   r   r	   r   s     

>r	   c                   @   s(   e Zd ZdZedd Zedd ZdS )r   zAn OAuth access token.c                 C   s&   |d }|d }| d}| |||S )z:Create and return a new `AccessToken` from the given dict.rQ   oauth_token_secretr@   )r   )r6   r%   rD   rR   rM   r   r   r   rJ      s   
zAccessToken.from_paramsc                 C   s   t |ts
|d}t|dd}|d }t|dksJ d|d }|d }t|dks0J d	|d }|d
}|durKt|dksGJ d|d }| |||S )z<Create and return a new `AccessToken` from the given string.r+   F)Zkeep_blank_valuesrQ   r   z/Query string must have exactly one oauth_token.r   r[   z*Query string must have exactly one secret.r@   Nz*Query string must have exactly one context)r.   r/   r4   r   lenr   )r6   Zquery_stringr%   rD   rR   rM   r   r   r   r9      s"   



zAccessToken.from_stringN)rU   rV   rW   rX   rZ   rJ   r9   r   r   r   r   r      s    
r   c                       s    e Zd ZdZ fddZ  ZS )r   zoAn OAuth access token that doesn't authenticate anybody.

    This token can be used for anonymous access.
    c                    s   t t| dd d S )N )superr   __init__r1   	__class__r   r   r_     s   zAnonymousAccessToken.__init__)rU   rV   rW   rX   r_   __classcell__r   r   ra   r   r     s    r   c                   @   s:   e Zd ZdZdddZdd Zdd Zd	d
 Zdd ZdS )r   zStore OAuth credentials locally.

    This is a generic superclass. To implement a specific way of
    storing credentials locally you'll need to subclass this class,
    and implement `do_save` and `do_load`.
    Nc                 C   s
   || _ dS )a  Constructor.

        :param credential_save_failed: A callback to be invoked if the
            save to local storage fails. You should never invoke this
            callback yourself! Instead, you should raise an exception
            from do_save().
        N)credential_save_failedr1   rd   r   r   r   r_   &  s   
zCredentialStore.__init__c              
   C   s^   z	|  || W |S  ty     ty. } z| jdu r||   W Y d}~|S d}~ww )zSave the credentials and invoke the callback on failure.

        Do not override this method when subclassing. Override
        do_save() instead.
        N)do_saveEXPLOSIVE_ERRORS	Exceptionrd   )r1   r8   unique_consumer_ider   r   r   r,   0  s   
zCredentialStore.savec                 C      t  )zStore newly-authorized credentials locally for later use.

        :param credentials: A Credentials object to save.
        :param unique_consumer_id: A string uniquely identifying an
            OAuth consumer on a Launchpad instance.
        NotImplementedError)r1   r8   ri   r   r   r   rf   @  s   zCredentialStore.do_savec                 C   s
   |  |S )a0  Retrieve credentials from a local store.

        This method is the inverse of `save`.

        There's no special behavior in this method--it just calls
        `do_load`. There _is_ special behavior in `save`, and this
        way, developers can remember to implement `do_save` and
        `do_load`, not `do_save` and `load`.

        :param unique_key: A string uniquely identifying an OAuth consumer
            on a Launchpad instance.

        :return: A `Credentials` object if one is found in the local
            store, and None otherise.
        )do_loadr1   
unique_keyr   r   r   r5   I  s   
zCredentialStore.loadc                 C   rk   )a@  Retrieve credentials from a local store.

        This method is the inverse of `do_save`.

        :param unique_key: A string uniquely identifying an OAuth consumer
            on a Launchpad instance.

        :return: A `Credentials` object if one is found in the local
            store, and None otherise.
        rl   ro   r   r   r   rn   [  s   zCredentialStore.do_loadN)	rU   rV   rW   rX   r_   r,   rf   r5   rn   r   r   r   r   r     s    

	r   c                       sB   e Zd ZdZdZd fdd	Zedd Zd	d
 Zdd Z	  Z
S )KeyringCredentialStorezStore credentials in the GNOME keyring or KDE wallet.

    This is a good solution for desktop applications and interactive
    scripts. It doesn't work for non-interactive scripts, or for
    integrating third-party websites into Launchpad.
    s   <B64>NFc                    s,   t t| | d | _|rt|| _d S d S rq   )r^   rr   r_   	_fallbackMemoryCredentialStore)r1   rd   fallbackra   r   r   r_   s  s
   zKeyringCredentialStore.__init__c                   C   sL   dt  vr	ddladt  vr$z	ddlma W dS  ty#   taY dS w dS )aG  Ensure the keyring module is imported (postponing side effects).

        The keyring module initializes the environment-dependent backend at
        import time (nasty).  We want to avoid that initialization because it
        may do things like prompt the user to unlock their password store
        (e.g., KWallet).
        keyringr   NNoKeyringError)rw   )globalsrv   Zkeyring.errorsrw   ImportErrorRuntimeErrorr   r   r   r   _ensure_keyring_importedy  s   
	

z/KeyringCredentialStore._ensure_keyring_importedc              
   C   s   |    | }| jt| }ztd||d W dS  tyF } zttkr/dt	|vr/ | j
r:| j
|| n W Y d}~dS d}~ww )z2Store newly-authorized credentials in the keyring.launchpadlibr+   $No recommended backend was availableN)r{   r3   	B64MARKERr   rv   Zset_passwordr4   rw   rz   strrs   r,   )r1   r8   rp   r2   rj   r   r   r   rf     s&   

zKeyringCredentialStore.do_savec              
   C   s   |    ztd|}W n' ty3 } zttkrdt|vr | jr.| j|W  Y d}~S  d}~ww |durst|t	rB|
d}|| jr`zt|t| jd }W n
 ty_   Y dS w zt|}|W S  tyr   Y dS w dS )z&Retrieve credentials from the keyring.r|   r}   Nutf8)r{   rv   Zget_passwordrw   rz   r   rs   r5   r.   r/   r0   
startswithr~   r   r\   	TypeErrorr	   r9   rh   )r1   rp   Zcredential_stringrj   r8   r   r   r   rn     sD   



zKeyringCredentialStore.do_load)NF)rU   rV   rW   rX   r~   r_   staticmethodr{   rf   rn   rc   r   r   ra   r   rr   i  s    
rr   c                       2   e Zd ZdZd	 fdd	Zdd Zdd Z  ZS )
UnencryptedFileCredentialStorezStore credentials unencrypted in a file on disk.

    This is a good solution for scripts that need to run without any
    user interaction.
    Nc                    s   t t| | || _d S rq   )r^   r   r_   filename)r1   r   rd   ra   r   r   r_     s   

z'UnencryptedFileCredentialStore.__init__c                 C   s   | | j dS )zSave the credentials to disk.N)Zsave_to_pathr   r1   r8   rp   r   r   r   rf     s   z&UnencryptedFileCredentialStore.do_savec                 C   s4   t j| jrt | jtj dkst| jS dS )zLoad the credentials from disk.r   N)r   pathexistsr   statST_SIZEr	   Zload_from_pathro   r   r   r   rn     s   z&UnencryptedFileCredentialStore.do_loadrq   rU   rV   rW   rX   r_   rf   rn   rc   r   r   ra   r   r     s
    r   c                       r   )
rt   zCredentialStore that stores keys only in memory.

    This can be used to provide a CredentialStore instance without
    actually saving any key to persistent storage.
    Nc                    s   t t| | i | _d S rq   )r^   rt   r_   _credentialsre   ra   r   r   r_     s   
zMemoryCredentialStore.__init__c                 C   s   || j |< dS )z!Store the credentials in our dictN)r   r   r   r   r   rf     s   zMemoryCredentialStore.do_savec                 C   s   | j |S )z&Retrieve the credentials from our dict)r   r   ro   r   r   r   rn     s   zMemoryCredentialStore.do_loadrq   r   r   r   ra   r   rt     s
    rt   c                   @   sP   e Zd ZdZdZ			dddZedd Zdd	 Zd
d Z	dd Z
dd ZdS )r   a/  The superclass of all request token authorizers.

    This base class does not implement request token authorization,
    since that varies depending on how you want the end-user to
    authorize a request token. You'll need to subclass this class and
    implement `make_end_user_authorize_token`.
    ZUNAUTHORIZEDNc                 C   s   t || _t || _|du r|du rtd|dur(|dur(td||f |du r4dg}t|}nt|}|}|| _|| _	|pCg | _
dS )aD  Base class initialization.

        :param service_root: The root of the Launchpad instance being
            used.

        :param application_name: The name of the application that
            wants to use launchpadlib. This is used in conjunction
            with a desktop-wide integration.

            If you specify this argument, your values for
            consumer_name and allow_access_levels are ignored.

        :param consumer_name: The OAuth consumer name, for an
            application that wants its own point of integration into
            Launchpad. In almost all cases, you want to specify
            application_name instead and do a desktop-wide
            integration. The exception is when you're integrating a
            third-party website into Launchpad.

        :param allow_access_levels: A list of the Launchpad access
            levels to present to the user. ('READ_PUBLIC' and so on.)
            Your value for this argument will be ignored during a
            desktop-wide integration.
        :type allow_access_levels: A list of strings.
        Nz:You must provide either application_name or consumer_name.zZYou must provide only one of application_name and consumer_name. (You provided %r and %r.)ZDESKTOP_INTEGRATION)r   Zlookup_service_rootservice_rootZweb_root_for_service_rootrN   
ValueErrorr   r   rA   application_nameallow_access_levels)r1   r   r   consumer_namer   rA   r   r   r   r_   	  s(    
z(RequestTokenAuthorizationEngine.__init__c                 C   s   | j jd | j S )z7Return a string identifying this consumer on this host.@)rA   rD   r   r`   r   r   r   ri   I  s   z2RequestTokenAuthorizationEngine.unique_consumer_idc                 C   s>   dt |f }d}t| jdkr|||| j 7 }t| j|S )zReturn the authorization URL for a request token.

        This is the URL the end-user must visit to authorize the
        token. How exactly does this happen? That depends on the
        subclass implementation.
        z%s?oauth_token=%sz&allow_permission=r   )rL   r\   r   joinr   rN   )r1   request_tokenZpageZallow_permissionr   r   r   authorization_urlN  s   z1RequestTokenAuthorizationEngine.authorization_urlc                 C   s6   |  |}| || |jdu rdS ||| j |S )ad  Authorize a token and associate it with the given credentials.

        If the credential store runs into a problem storing the
        credential locally, the `credential_save_failed` callback will
        be invoked. The callback will not be invoked if there's a
        problem authorizing the credentials.

        :param credentials: A `Credentials` object. If the end-user
            authorizes these credentials, this object will have its
            .access_token property set.

        :param credential_store: A `CredentialStore` object. If the
            end-user authorizes the credentials, they will be
            persisted locally using this object.

        :return: If the credentials are successfully authorized, the
            return value is the `Credentials` object originally passed
            in. Otherwise the return value is None.
        N)rP   make_end_user_authorize_tokenrB   r,   ri   )r1   r8   Zcredential_storeZrequest_token_stringr   r   r   __call__]  s   

z(RequestTokenAuthorizationEngine.__call__c                 C   s   |j | jtjd}|d S )z\Get a new request token from the server.

        :param return: The request token.
        )rN   rO   rQ   )rP   rN   r	   rF   )r1   r8   Zauthorization_jsonr   r   r   rP   {  s   z1RequestTokenAuthorizationEngine.get_request_tokenc                 C   rk   )a5  Authorize the given request token using the given credentials.

        Your subclass must implement this method: it has no default
        implementation.

        Because an access token may expire or be revoked in the middle
        of a session, this method may be called at arbitrary points in
        a launchpadlib session, or even multiple times during a single
        session (with a different request token each time).

        In most cases, however, this method will be called at the
        beginning of a launchpadlib session, or not at all.
        rl   )r1   r8   r   r   r   r   r     s   z=RequestTokenAuthorizationEngine.make_end_user_authorize_tokenNNN)rU   rV   rW   rX   ZUNAUTHORIZED_ACCESS_LEVELr_   propertyri   r   r   rP   r   r   r   r   r   r     s    
@

r   c                   @   s@   e Zd ZdZdZdZdd Zdd Zdd	 Zd
d Z	dd Z
dS )AuthorizeRequestTokenWithURLzAuthorize using a URL.

    This authorizer simply shows the URL for the user to open for
    authorization, and waits until the server responds.
    zPlease open this authorization page:
 (%s)
in your browser. Use your browser to authorize
this program to access Launchpad on your behalf.z.Press Enter after authorizing in your browser.c                 C   s   t | dS )zDisplay a message.

        By default, prints the message to standard output. The message
        does not require any user interaction--it's solely
        informative.
        N)print)r1   messager   r   r   output  s   z#AuthorizeRequestTokenWithURL.outputc                 C   s   |  | j|  dS )Notify the end-user of the URL.N)r   WAITING_FOR_USER)r1   r   r   r   r   !notify_end_user_authorization_url  s   z>AuthorizeRequestTokenWithURL.notify_end_user_authorization_urlc              
   C   sp   z| | j W n* ty2 } z|jjdkrt|j|jjdkr)td t| t|jd}~ww |j	duS )z Check if the end-user authorizedi  i  z#Unexpected response from Launchpad:N)
rT   rN   r   r&   r#   EndUserDeclinedAuthorizationr'   r   EndUserNoAuthorizationrB   )r1   r8   rj   r   r   r   check_end_user_authorization  s   


z9AuthorizeRequestTokenWithURL.check_end_user_authorizationc                 C   s"   |  | j t  | | dS )"Wait for the end-user to authorizeN)r   WAITING_FOR_LAUNCHPADr   readliner   )r1   r8   r   r   r   wait_for_end_user_authorization  s   z<AuthorizeRequestTokenWithURL.wait_for_end_user_authorizationc                 C   s"   |  |}| | | | dS )z2Have the end-user authorize the token using a URL.N)r   r   r   )r1   r8   r   r   r   r   r   r     s   

z:AuthorizeRequestTokenWithURL.make_end_user_authorize_tokenN)rU   rV   rW   rX   r   r   r   r   r   r   r   r   r   r   r   r     s    	r   c                       sP   e Zd ZdZdZdZdZdZdZ			d fdd		Z	 fd
dZ
dd Z  ZS )r   aS  Authorize using a URL that pops-up automatically in a browser.

    This authorizer simply opens up the end-user's web browser to a
    Launchpad URL and lets the end-user authorize the request token
    themselves.

    This is the same as its superclass, except this class also
    performs the browser automatic opening of the URL.
    zThe authorization page:
 (%s)
should be opening in your browser. Use your browser to authorize
this program to access Launchpad on your behalf.z/Press Enter to continue or wait (%d) seconds...   )zwww-browserZlinksZlinks2ZlynxZelinkszelinks-liteZnetrikZw3mz5Waiting to hear from Launchpad about your decision...Nc                    s   t t| ||d| dS )ao  Constructor.

        :param service_root: See `RequestTokenAuthorizationEngine`.
        :param application_name: See `RequestTokenAuthorizationEngine`.
        :param consumer_name: The value of this argument is
            ignored. If we have the capability to open the end-user's
            web browser, we must be running on the end-user's computer,
            so we should do a full desktop integration.
        :param credential_save_failed: See `RequestTokenAuthorizationEngine`.
        :param allow_access_levels: The value of this argument is
            ignored, for the same reason as consumer_name.
        N)r^   r   r_   )r1   r   r   r   rd   r   ra   r   r   r_     s   
z)AuthorizeRequestTokenWithBrowser.__init__c                    s   t t| | zt }t|dd}|| jv }W n tjy'   d}d}Y nw |rE| | j	| j
  ttgg g | j
\}}}|rEt  |durPt| dS dS )r   basenameNF)r^   r   r   
webbrowserr   getattrTERMINAL_BROWSERSErrorr   TIMEOUT_MESSAGETIMEOUTr   r   r   open)r1   r   Zbrowser_objZbrowserZconsole_browserZrlist_ra   r   r   r     s(   zBAuthorizeRequestTokenWithBrowser.notify_end_user_authorization_urlc                 C   s~   |  | j t }|jdu r=tt z
| |rW dS W n	 ty'   Y nw t |t kr6t	dt |jdu sdS dS )r   NzTimed out after %d seconds.)
r   r   timerB   sleepaccess_token_poll_timer   r   access_token_poll_timeoutTokenAuthorizationTimedOut)r1   r8   Z
start_timer   r   r   r   &  s    


z@AuthorizeRequestTokenWithBrowser.wait_for_end_user_authorizationr   )rU   rV   rW   rX   r   r   r   r   r   r_   r   r   rc   r   r   ra   r   r     s    r   c                   @      e Zd ZdS )TokenAuthorizationExceptionNrU   rV   rW   r   r   r   r   r   7      r   c                   @   r   )RequestTokenAlreadyAuthorizedNr   r   r   r   r   r   ;  r   r   c                   @      e Zd ZdZdS )EndUserAuthorizationFailedz?Superclass exception for all failures of end-user authorizationNrU   rV   rW   rX   r   r   r   r   r   ?      r   c                   @   r   )r   zEnd-user declined authorizationNr   r   r   r   r   r   E  r   r   c                   @   r   )r   z*End-user did not perform any authorizationNr   r   r   r   r   r   K  r   r   c                   @   r   )r   z<End-user did not perform any authorization in timeout periodNr   r   r   r   r   r   Q  r   r   c                   @   r   )ClientErrorNr   r   r   r   r   r   W  r   r   c                   @   r   )ServerErrorNr   r   r   r   r   r   [  r   r   c                   @   r   )NoLaunchpadAccountNr   r   r   r   r   r   _  r   r   c                   @   r   )TooManyAuthenticationFailuresNr   r   r   r   r   r   c  r   r   )JZ
__future__r   typeZ__metaclass____all__	cStringIOr
   ry   ior"   rH   r   r   r   sysr   r   Zurllib.parser   Zurllibr   Zurlparser   base64r   r   Zsix.moves.urllib.parser   rG   r   Zunicoder/   Zlazr.restfulclient.errorsr   Z"lazr.restfulclient.authorize.oauthr   Z_AccessTokenr   r   r   r|   r   rE   rS   rL   r   r   MemoryErrorKeyboardInterrupt
SystemExitrg   r   r(   r	   r   objectr   rr   r   rt   r   r   r   rh   r   r   r   r   r   r   r   r   r   r   r   r   r   r   <module>   s   

  
Kf ;f