U
    äVc/J  ć                   @   s   d Z ddlmZ ddlmZmZ ddlmZmZm	Z	m
Z
 ddlmZ ddlZddlZdZe e”Zdd	 Zd
d Zdd Zdd Zdd ZG dd deZdS )a  
Implements the `LockManager` object that provides the locking functionality.

The LockManager requires a LockStorage object to implement persistence.
Two alternative lock storage classes are defined in the lock_storage module:

- wsgidav.lock_storage.LockStorageDict
- wsgidav.lock_storage.LockStorageShelve


The lock data model is a dictionary with these fields:

    root:
        Resource URL.
    principal:
        Name of the authenticated user that created the lock.
    type:
        Must be 'write'.
    scope:
        Must be 'shared' or 'exclusive'.
    depth:
        Must be '0' or 'infinity'.
    owner:
        String identifying the owner.
    timeout:
        Seconds remaining until lock expiration.
        This value is passed to create() and refresh()
    expire:
        Converted timeout for persistence: expire = time() + timeout.
    token:
        Automatically generated unique token.

é    )Śpformat)ŚcompatŚutil)ŚDAVErrorŚDAVErrorConditionŚHTTP_LOCKEDŚPRECONDITION_CODE_LockConflict)ŚReadWriteLockNŚreStructuredTextc                   C   s   dt  tt d”” S )Nzopaquelocktoken:é   )r   Ś	to_nativeŚhexŚrandomŚgetrandbits© r   r   ś$/opt/wsgidav/wsgidav/lock_manager.pyŚgenerate_lock_token?   s    r   c                 C   s$   | st t | ”} d|  d” } | S )Nś/)ŚAssertionErrorr   r   Śstrip)Śpathr   r   r   Śnormalize_lock_rootC   s    
r   c                 C   s    t | d }|dko|t ” k S )NŚexpirer   )ŚfloatŚtime)Ślockr   r   r   r   Śis_lock_expiredK   s    r   c              	   C   s   | sdS | d dk r$d  | d ”}n"d  t | d ”| d t ”  ”}d  |  dd”d	d
 |  d”|  d”|  d”|  d”|”S )zReturn readable rep.z
Lock: Noner   r   zInfinite ({})z{} (in {} seconds)z-Lock(<{}..>, '{}', {}, {}, depth-{}, until {}Śtokenz??????????????????????????????é   é   ŚrootŚ	principalŚscopeŚdepth)Śformatr   Śget_log_timer   Śget)Ś	lock_dictr   r   r   r   Ślock_stringP   s      ’łr(   c                 C   s¾   t  | d ”st| d  d”s$t| d dks4t| d dksDt| d dksTtt  | d	 ”sjt| t| d
 }|dks|dkstdt  | d ”s td| krŗt  | d ”sŗtd S )Nr    r   ŚtypeŚwriter"   ©ŚsharedŚ	exclusiver#   ©Ś0ŚinfinityŚownerŚtimeoutr   é’’’’ztimeout must be positive or -1r!   r   )r   Ś	is_nativer   Ś
startswithŚis_bytesr   )r   r2   r   r   r   Śvalidate_lockg   s    r7   c                   @   s¤   e Zd ZdZdZdd Zdd Zdd Zd'd
dZdd Z	dd Z
d(ddZd)ddZdd Zdd Zdd Zd*ddZdd Zdd  Zd!d" Zd#d$ Zd%d& ZdS )+ŚLockManagerzI
    Implements locking functionality using a custom storage layer.

    i:	 c                 C   s*   t |dstt | _|| _| j ”  dS )z@
        storage:
            LockManagerStorage object
        Śget_lock_listN)Śhasattrr   r	   Ś_lockŚstorageŚopen)Śselfr<   r   r   r   Ś__init__   s    zLockManager.__init__c                 C   s   | j  ”  d S )N)r<   Śclose©r>   r   r   r   Ś__del__   s    zLockManager.__del__c                 C   s   d  | jj| j”S )Nz{}({!r}))r$   Ś	__class__Ś__name__r<   rA   r   r   r   Ś__repr__   s    zLockManager.__repr__Ś c              	   C   s  i }i }i }i }t  d | |”” | jjdddddD ]Z}|d }t|||< | |d g ” |” | |d g ” |” | |d	 g ” |” q6t  d
 t|ddd”” |ržt  d t|ddd”” t  d t|ddd”” t  d t|ddd”” d S )Nz{}: {}r   TF©Zinclude_rootZinclude_childrenZ
token_onlyr   r!   r1   r    z	Locks:
{}r   é’   )ŚindentŚwidthzLocks by URL:
{}é   zLocks by principal:
{}zLocks by owner:
{})	Ś_loggerŚinfor$   r<   r9   r(   Ś
setdefaultŚappendr   )r>   ŚmsgZurlDictZ	ownerDictZuserDictZ	tokenDictr   Śtokr   r   r   Ś_dump   s6       ’
’’’zLockManager._dumpc           	      C   sB   |dkrt j}n|dk rd}|||||||d}| j ||” |S )aå  Acquire lock and return lock_dict.

        principal
            Name of the principal.
        lock_type
            Must be 'write'.
        lock_scope
            Must be 'shared' or 'exclusive'.
        lock_depth
            Must be '0' or 'infinity'.
        lock_owner
            String identifying the owner.
        path
            Resource URL.
        timeout
            Seconds to live

        This function does NOT check, if the new lock creates a conflict!
        Nr   r3   )r    r)   r"   r#   r1   r2   r!   )r8   ŚLOCK_TIME_OUT_DEFAULTr<   Ścreate)	r>   r!   Ś	lock_typeŚ
lock_scopeŚ
lock_depthŚ
lock_ownerr   r2   r'   r   r   r   Ś_generate_lock³   s    ł
zLockManager._generate_lockc	           	   	   C   sR   t |}| j ”  z.|  ||||||” |  |||||||”W ¢S | j ”  X dS )zØCheck for permissions and acquire a lock.

        On success return new lock dictionary.
        On error raise a DAVError with an embedded DAVErrorCondition.
        N)r   r;   Śacquire_writeŚreleaseŚ_check_lock_permissionrY   )	r>   ŚurlrU   rV   rW   rX   r2   r!   Ś
token_listr   r   r   ŚacquireŪ   s*    
     ’      ’zLockManager.acquireNc                 C   s   |dkrt j}| j ||”S )z0Set new timeout for lock, if existing and valid.N)r8   rS   r<   Śrefresh)r>   r   r2   r   r   r   r`   ų   s    zLockManager.refreshc                 C   s4   |dkst | j |”}|dks(|dkr,|S || S )zīReturn lock_dict, or None, if not found or invalid.

        Side effect: if lock is expired, it will be purged and None is returned.

        key:
            name of lock attribute that will be returned instead of a dictionary.
        )	Nr)   r"   r#   r1   r    r2   r!   r   N)r   r<   r&   )r>   r   Śkeyr   r   r   r   Śget_lockž   s
    zLockManager.get_lockc                 C   s   | j  |” dS )zDelete lock.N)r<   Śdelete)r>   r   r   r   r   r[     s    zLockManager.releasec                 C   s   |   |d”|kS )zCReturn True, if <token> exists, is valid, and bound to <principal>.r!   )rb   )r>   r   r!   r   r   r   Śis_token_locked_by_user  s    z#LockManager.is_token_locked_by_userc                 C   s    t |}| jj|dddd}|S )zReturn list of lock_dict, if <url> is protected by at least one direct, valid lock.

        Side effect: expired locks for this url are purged.
        TFrG   )r   r<   r9   ©r>   r]   ŚlockListr   r   r   Śget_url_lock_list  s       ’zLockManager.get_url_lock_listc                 C   sv   t |}g }|}|rr| jj|dddd}|D ]8}||krF|d dkrFq,|dksZ||d kr,| |” q,t |”}q|S )zöReturn a list of valid lockDicts, that protect <path> directly or indirectly.

        If a principal is given, only locks owned by this principal are returned.
        Side effect: expired locks for this path and all parents are purged.
        TFrG   r#   r0   Nr!   )r   r<   r9   rO   r   Śget_uri_parent)r>   r]   r!   rf   ŚuŚ	lock_listr   r   r   r   Śget_indirect_url_lock_list)  s"       ’z&LockManager.get_indirect_url_lock_listc                 C   s   |   |”}t|dkS )z'Return True, if url is directly locked.r   )rg   Ślenre   r   r   r   Śis_url_lockedB  s    
zLockManager.is_url_lockedc                 C   s   |   |d”}|ot ||”S )z?Check, if url (or any of it's parents) is locked by lock_token.r    )rb   r   Śis_equal_or_child_uri)r>   r]   Ś
lock_tokenZlockUrlr   r   r   Śis_url_locked_by_tokenG  s    z"LockManager.is_url_locked_by_tokenc                 C   sB   | j  ”  z&|  |”}|D ]}|  |d ” qW 5 | j  ”  X d S )Nr   )r;   rZ   r[   rg   )r>   r]   rf   r   r   r   r   Śremove_all_locks_from_urlL  s    

z%LockManager.remove_all_locks_from_urlc                 C   sl  |dkst |dkst |dks$t t d ||||”” tt}| j ”  zņ|}|rÜ|  	|”}	|	D ]j}
t d |t
|
”” ||kr|
d dkrqdn|
d d	kr¬|d	kr¬qdt d
 t
|
”” | |
d ” qdt |”}qR|dkr<| jj|dddd}|D ]<}
t ||
d ”st t d t
|
”” | |
d ” qžW 5 | j ”  X t|jdkrhtt|ddS )au  Check, if <principal> can lock <url>, otherwise raise an error.

        If locking <url> would create a conflict, DAVError(HTTP_LOCKED) is
        raised. An embedded DAVErrorCondition contains the conflicting resource.

        @see http://www.webdav.org/specs/rfc4918.html#lock-model

        - Parent locks WILL NOT be conflicting, if they are depth-0.
        - Exclusive depth-infinity parent locks WILL be conflicting, even if
          they are owned by <principal>.
        - Child locks WILL NOT be conflicting, if we request a depth-0 lock.
        - Exclusive child locks WILL be conflicting, even if they are owned by
          <principal>. (7.7)
        - It is not enough to check whether a lock is owned by <principal>, but
          also the token must be passed with the request. (Because <principal>
          may run two different applications on his client.)
        - <principal> cannot lock-exclusive, if he holds a parent shared-lock.
          (This would only make sense, if he was the only shared-lock holder.)
        - TODO: litmus tries to acquire a shared lock on one resource twice
          (locks: 27 'double_sharedlock') and fails, when we return HTTP_LOCKED.
          So we allow multi shared locks on a resource even for the same
          principal.

        @param url: URL that shall be locked
        @param lock_type: "write"
        @param lock_scope: "shared"|"exclusive"
        @param lock_depth: "0"|"infinity"
        @param token_list: list of lock tokens, that the user submitted in If: header
        @param principal: name of the principal requesting a lock

        @return: None (or raise)
        r*   r+   r.   z#checkLockPermission({}, {}, {}, {})z    check parent {}, {}r#   r0   r"   r,   ś" -> DENIED due to locked parent {}r    FTrG   ś! -> DENIED due to locked child {}r   ©Śerr_conditionN)r   rL   Śdebugr$   r   r   r;   Śacquire_readr[   rg   r(   Śadd_hrefr   rh   r<   r9   Śis_child_urirl   Śhrefsr   r   )r>   r]   rU   rV   rW   r^   r!   Śerrcondri   rj   r   Ś
child_ocksr   r   r   r\   U  s^    #   ’’

’’
   ’’z"LockManager._check_lock_permissionc           
      C   sz  t  |”st|dkstt d ||||”” tt}| j 	”  z|}|rč|  |”}t d |”” |D ]n}t d t|”” ||kr|d dkrqlql||d krø|d |krøqlqlt d	 t|”” | |d
 ” qlt |”}qJ|dkrJ| jj|dddd}	|	D ]>}t ||d
 ”s$tt d t|”” | |d
 ” q
W 5 | j 
”  X t|jdkrvtt|ddS )a  Check, if <principal> can modify <url>, otherwise raise HTTP_LOCKED.

        If modifying <url> is prevented by a lock, DAVError(HTTP_LOCKED) is
        raised. An embedded DAVErrorCondition contains the conflicting locks.

        <url> may be modified by <principal>, if it is not currently locked
        directly or indirectly (i.e. by a locked parent).
        For depth-infinity operations, <url> also must not have locked children.

        It is not enough to check whether a lock is owned by <principal>, but
        also the token must be passed with the request. Because <principal> may
        run two different applications.

        See http://www.webdav.org/specs/rfc4918.html#lock-model
            http://www.webdav.org/specs/rfc4918.html#rfc.section.7.4

        TODO: verify assumptions:
        - Parent locks WILL NOT be conflicting, if they are depth-0.
        - Exclusive child locks WILL be conflicting, even if they are owned by <principal>.

        @param url: URL that shall be modified, created, moved, or deleted
        @param depth: "0"|"infinity"
        @param token_list: list of lock tokens, that the principal submitted in If: header
        @param principal: name of the principal requesting a lock

        @return: None or raise error
        r.   z&check_write_permission({}, {}, {}, {})z  checking {}z     lock={}r#   r0   r!   r   rr   r    FTrG   rs   r   rt   N)r   r4   r   rL   rv   r$   r   r   r;   rw   r[   rg   r(   rx   r   rh   r<   r9   ry   rl   rz   r   r   )
r>   r]   r#   r^   r!   r{   ri   rj   r   r|   r   r   r   Ścheck_write_permission³  s^       ’’

’’
   ’’z"LockManager.check_write_permission)rF   )N)N)N)rD   Ś
__module__Ś__qualname__Ś__doc__rS   r?   rB   rE   rR   rY   r_   r`   rb   r[   rd   rg   rk   rm   rp   rq   r\   r}   r   r   r   r   r8   y   s&   

"(


	^r8   )r   Śpprintr   Śwsgidavr   r   Śwsgidav.dav_errorr   r   r   r   Zwsgidav.rw_lockr	   r   r   Ś__docformat__Śget_module_loggerrD   rL   r   r   r   r(   r7   Śobjectr8   r   r   r   r   Ś<module>   s   !
