U
    cx                     @   s   d 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 ddlmZmZmZ ddlZddlZddlZddlZd	ZeeZd
d ZG dd deZdS )a:  
WSGI middleware for HTTP basic and digest authentication.

Usage::

   from http_authenticator import HTTPAuthenticator

   WSGIApp = HTTPAuthenticator(ProtectedWSGIApp, domain_controller, accept_basic,
                               accept_digest, default_to_digest)

   where:
     ProtectedWSGIApp is the application requiring authenticated access

     domain_controller is a domain controller object meeting specific
     requirements (below)

     accept_basic is a boolean indicating whether to accept requests using
     the basic authentication scheme (default = True)

     accept_digest is a boolean indicating whether to accept requests using
     the digest authentication scheme (default = True)

     default_to_digest is a boolean. if True, an unauthenticated request will
     be sent a digest authentication required response, else the unauthenticated
     request will be sent a basic authentication required response
     (default = True)

The HTTPAuthenticator will put the following authenticated information in the
environ dictionary::

   environ["wsgidav.auth.realm"] = realm name
   environ["wsgidav.auth.user_name"] = user_name
   environ["wsgidav.auth.roles"] = <tuple> (optional)
   environ["wsgidav.auth.permissions"] = <tuple> (optional)


**Domain Controllers**

The HTTP basic and digest authentication schemes are based on the following
concept:

Each requested relative URI can be resolved to a realm for authentication,
for example:
/fac_eng/courses/ee5903/timetable.pdf -> might resolve to realm 'Engineering General'
/fac_eng/examsolns/ee5903/thisyearssolns.pdf -> might resolve to realm 'Engineering Lecturers'
/med_sci/courses/m500/surgery.htm -> might resolve to realm 'Medical Sciences General'
and each realm would have a set of user_name and password pairs that would
allow access to the resource.

A domain controller provides this information to the HTTPAuthenticator.
This allows developers to write their own domain controllers, that might,
for example, interface with their own user database.

for simple applications, a SimpleDomainController is provided that will take
in a single realm name (for display) and a single dictionary of user_name (key)
and password (value) string pairs

Usage::

   from wsgidav.dc.simple_dc import SimpleDomainController
   users = dict(({'John Smith': 'YouNeverGuessMe', 'Dan Brown': 'DontGuessMeEither'})
   realm = 'Sample Realm'
   domain_controller = SimpleDomainController(users, realm)


Domain Controllers must provide the methods as described in
``wsgidav.interfaces.domaincontrollerinterface`` (interface_)

.. _interface : interfaces/domaincontrollerinterface.py

The environ variable here is the WSGI 'environ' dictionary. It is passed to
all methods of the domain controller as a means for developers to pass information
from previous middleware or server config (if required).
    )md5)dedent)compatutil)SimpleDomainController)BaseMiddleware)calc_base64calc_hexdigestdynamic_import_classNreStructuredTextc                 C   sb   | di  d}|}|dks"|s(t}nt|r:t|}t|rP|| |}ntd||S )Nhttp_authenticatordomain_controllerTz2Could not resolve domain controller class (got {}))	getr   r   is_basestringr
   inspectisclassRuntimeErrorformat)wsgidav_appconfigdcZorg_dc r   */opt/wsgidav/wsgidav/http_authenticator.pymake_domain_controllerb   s    

r   c                       s   e Zd ZdZedZ fddZdd Zdd Ze	
d	Zd
d Zdd Zdd Zdd Zdd Zdd Zdd Zdd Z  ZS )HTTPAuthenticatorz4WSGI Middleware for basic and digest authentication.z        <html>
            <head><title>401 Access not authorized</title></head>
            <body>
                <h1>401 Access not authorized</h1>
            </body>
        </html>
    c                    sD  t t| ||| |dd| _|| _t||}|| _|di }|dd| _|dd| _	|di }|dd	| _
|d
d	| _|dd	| _|dd | _| s| js| js| j
std|jjtg | _td| _td| _td| _ddlm} ||di d| _d | _| j r@| j | _d S )Nverbose   hotfixeswinxp_accept_root_share_loginFwin_accept_anonymous_optionsr   accept_basicTaccept_digestdefault_to_digesttrusted_auth_headerzn{} does not support digest authentication.
Set accept_basic=True, accept_digest=False, default_to_digest=Falsez([\w]+)=([^,]*),z([\w]+)=("[^"]*,[^"]*"),z^([\w]+)r   )PathZacrm_dcpub_key_path) superr   __init__r   _verboser   r   r   r   r   r    r!   r"   r#   Zsupports_http_digest_authr   r   	__class____name__dictZ_nonce_dictrecompile_header_parser_header_fix_parser_header_methodpathlibr$   r%   pub_keyexists
read_bytes)selfr   next_appr   r   r   	auth_confr$   r)   r   r   r'      sP    
  
zHTTPAuthenticator.__init__c                 C   s   | j S N)r   )r5   r   r   r   get_domain_controller   s    z'HTTPAuthenticator.get_domain_controllerc                 C   s   | j |d S r9   )r   require_authentication)r5   Zsharer   r   r   allow_anonymous_access   s    z(HTTPAuthenticator.allow_anonymous_accessz)^s_[a-zA-Z0-9_@.-]+_[a-zA-Z0-9@\.]{8}_87$c           	      C   s   dd l }dd l}z|j|| jddgd}W n |jk
rB   Y d S X |d }|d }|dp`d }||d	< | |d
< dg|d< ||d< ||d< d S )Nr   ZRS256ZHS256)keyZ
algorithmsloginexpscope wsgidav.auth.user_namewsgidav.auth.realm*wsgidav.auth.roleszwsgidav.auth.jwtzwsgidav.auth.jwt_decoded)socketjwtdecoder2   ZInvalidTokenErrorr   splitgethostname)	r5   environZjwt_contentrF   rG   Zjwt_decodedr>   r?   r@   r   r   r   set_environ_from_jwt   s    
z&HTTPAuthenticator.set_environ_from_jwtc                 C   s  | j |d |}||d< d|d< d |d< d |d< d}d|d	dkrTd
}td d}| jrx|d dkrxtd d
}|s| j ||s| ||S d }d }|d d}|st	|dkr|d }| j
|rdd l}	dd l}
dd l}|dd }|	d| d| }| s$td| }|
|}|d }|d }|d}|| k rftdd|d|d i}|r||krtddd|dd   |d< t| d d |d< ||d< ||
| dd l}|j|d}d}|s,|r,|d }|r,|jd!kr,|j}d
}td"t|  |sJd }t|dd# t|d$d% |rZtd& | || |d r8td' |r,td( |d$}|std) |  ||S |d*d+ }dd,l!m"} ||# }|d-d }|d |kr,td. |  ||S | ||S |d$sZtd/ |  ||S | j$r|| j$rtd0%| j$|| j$| || j$|d< | ||S d$|kr|s|d$ }| j&'|}d!}|r|(d) }|d1kr| j*r| +||S |d1kr"| j,r"| -||S |d2kr@| j,r@| .||S | j/r\| j*r\| 0||S | j,rp| -||S td3%| |d4d5d6t12 fg dgS | j/r| 0||S | -||S )7N	PATH_INFOrC   rA   rB   rE   zwsgidav.auth.permissionsFZlogoutQUERY_STRINGTzForce logoutREQUEST_METHODZOPTIONSz,No authorization required for OPTIONS method/      r   _z/opt/rdisk/sessions/u    Сессия не найденаrG   r?   ZCLIENT_DATAu1   Срок действия сессии истекZREMOTE_ADDRz	X-Real-IPu   Изменился клиентiQ    ZHTTP_COOKIEZaccess_tokenNonezASDJKLJLKASDKLJASD u   НЕТ КУКИHTTP_AUTHORIZATIONu   НЕТ HTTP_AUTHORIZATIONzif jwt:zif wsgidav.auth.user_namezif jwt_from_cookiezif not http_auth )	b64decode:zif user_name != loginz!if wsgidav.auth.user_name: | elsez.Accept trusted user_name {}='{}'for realm '{}'digestZbasicz@HTTPAuthenticator: respond with 400 Bad request; Auth-Method: {}z400 Bad Request)Content-Length0Date)3r   get_domain_realmr   _loggerwarningr   r;   r6   rI   lensession_name_regexmatchr1   jsontimer$   r3   	Exception	read_textloadsjoinintZ
write_textdumpsZhttp.cookiesZcookiesZSimpleCookievalueinforeprdebugrL   $send_basic_auth_response_flush_tokenbase64rY   rH   r#   r   r0   searchgrouplowerr!   handle_digest_auth_requestr    send_basic_auth_responsehandle_basic_auth_requestr"   send_digest_auth_responser   get_rfc1123_time)r5   rK   start_responserealmZforce_logoutZforce_allowrG   Zsession_nameZpath_info_splitr1   re   rf   r>   Z	json_pathZjson_strZ	json_datar?   Zsaved_client_dataZenviron_client_datahttpZcookieZjwt_from_cookieZaccess_token_cookieZ	http_authZlogin_pass_b64rY   Z
login_passauth_headerZ
auth_matchZauth_methodr   r   r   __call__   s    


 


 









zHTTPAuthenticator.__call__c              
   C   s   | j |d |}td| d| d }t| j}dd|	d
dd 
dd	d   }|d
d|fddtt|fdt fdd| dfg |gS )NrM   z0!@#!@# 401 Not Authorized for realm '{}' (basic)Basic realm="".	HTTP_HOSTrZ   r   401 Not AuthorizedWWW-AuthenticatezContent-Typez	text/htmlr\   r^   z
Set-Cookiez"access_token=None; path=/; domain=z'; expires=Thu, 02 Jan 2070 00:00:00 GMT)r   r_   r`   rp   r   r   to_byteserror_message_401rj   r   rI   strrb   r   rz   )r5   rK   r{   r|   wwwauthheadersbodydomainr   r   r   rq     s    ,

z6HTTPAuthenticator.send_basic_auth_response_flush_tokenc                 C   sj   | j |d |}td| d| d }t| j}|dd|fddtt	|fd	t
 fg |gS )
NrM   z)401 Not Authorized for realm '{}' (basic)r   r   r   r   r   r\   r^   )r   r_   r`   rp   r   r   r   r   r   rb   r   rz   )r5   rK   r{   r|   r   r   r   r   r   rw     s    
	z*HTTPAuthenticator.send_basic_auth_responsec                 C   s   | j |d |}|d }d}z|tdd   }W n tk
rN   d}Y nX tt|}t|}|	dd\}}| j 
||||r||d< ||d< | ||S td	|| | ||S )
NrM   rV   rA   zBasic rZ   rR   rC   rB   z8Authentication (basic) failed for user '{}', realm '{}'.)r   r_   rb   striprg   r   base64_decodebytesr   	to_nativerI   Zbasic_auth_userr6   r`   ra   r   rw   )r5   rK   r{   r|   r~   Z
auth_value	user_namepasswordr   r   r   rx     s*    

 z+HTTPAuthenticator.handle_basic_auth_requestc                 C   s   | j |d |}t  ttddd  }t|d }tt }|t|d | d |  }t	|}d
||}	td
||	 t| j}
|dd|	fd	d
tt|
fdt fg |
gS )NrM       rQ   rZ   z8Digest realm="{}", nonce="{}", algorithm=MD5, qop="auth"z.401 Not Authorized for realm '{}' (digest): {}r   r   r   r\   r^   )r   r_   randomseedhexgetrandbitsr	   r   rf   r   r   r`   rp   r   r   r   rb   r   rz   )r5   rK   r{   r|   Z	serverkeyZetagkeyZtimekeyZnonce_sourcenoncer   r   r   r   r   ry     s<      
	z+HTTPAuthenticator.send_digest_auth_responsec                 C   s  | j |d |}d}g }i }|d d }|  dsPd}|d| | j|}| j	|}	|	rt
d||	 ||	7 }|D ]&}
|
d	 }|
d
  d}|||< qd }d|kr|d }|sd}|d| n,d|kr|}|dd}t
d|| nd}|d d|krt|d  | krt| jr`|d dkr`t
d nd}|d| d|kr|d  dkrd}|d |d}d|kr|d }nd}|d d}d|krd}|d }| dkrd}|d nd }d|kr|d }nd }|r2d}|d  d!|krF|d! }nd }|r^d}|d" d#|krr|d# }nd}|d$ |sL|d% }| |||||||||	}|sd}|d&|| n||krLd'||||}| jr<|dkr<| d||||||||	}||kr(t
d(| nd}||d)  nd}|| n |r|d*| | jd+krt
d,||d-| nt
d.|| | ||S ||d/< ||d0< | ||S )1NrM   FrV   ,r[   Tz/HTTP_AUTHORIZATION must start with 'digest': {}z3Fixing auth_header comma-parsing: extend {} with {}r   rR   r   usernamez`username` is empty: {!r}z\\\z8Fixing Windows name with double backslash: '{}' --> '{}'zMissing 'username' in headersr|   rP   r   zRealm mismatch: '{}'	algorithmMD5z"Unsupported 'algorithm' in headersurir   zExpected 'nonce' in headersqopauthzExpected 'qop' == 'auth'cnoncez-Expected 'cnonce' in headers if qop is passedncz)Expected 'nc' in headers if qop is passedresponsezExpected 'response' in headersrO   z+Rejected by DC.digest_auth_user('{}', '{}')z2compute_digest_response('{}', '{}', ...): {} != {}zAhandle_digest_auth_request: HOTFIX: accepting '/' login for '{}'.z (also tried '/' realm)zHeaders:
    {}   z>Authentication (digest) failed for user '{}', realm '{}':
  {}z
  z9Authentication (digest) failed for user '{}', realm '{}'.rC   rB   )r   r_   ru   r   
startswithappendr   r.   findallr/   r`   rn   replaceupperr   r   compute_digest_responsera   r(   rj   ry   r6   )r5   rK   r{   r|   Zis_invalid_reqZinvalid_req_reasonsZauth_header_dictZauth_headersZauth_header_listZauth_header_fixlistr~   Zauth_header_keyZauth_header_valueZreq_usernameZreq_username_orgZreq_uriZ	req_nonceZreq_has_qopZreq_qopZ
req_cnonceZreq_ncZreq_responseZ
req_methodZrequired_digestZwarning_msgZroot_digestr   r   r   rv     s@   	 


 

















 
   
   z,HTTPAuthenticator.handle_digest_auth_requestc
                    s   dd   fdd}
| j |||	}|s,dS |d | }|rl|
||d | d | d | d  | }n|
||d  | }|S )a`  Computes digest hash.

        Calculation of the A1 (HA1) part is delegated to the dc interface method
        `digest_auth_user()`.

        Args:
            realm (str):
            user_name (str):
            method (str): WebDAV Request Method
            uri (str):
            nonce (str): server generated nonce value
            cnonce (str): client generated cnonce value
            qop (str): quality of protection
            nc (str) (number), nonce counter incremented by client
        Returns:
            MD5 hash string
            or False if user rejected by domain controller
        c                 S   s   t t|  S r9   )r   r   r   	hexdigest)datar   r   r   md5h  s    z7HTTPAuthenticator.compute_digest_response.<locals>.md5hc                    s    | d | S )NrZ   r   )Zsecretr   r   r   r   md5kd  s    z8HTTPAuthenticator.compute_digest_response.<locals>.md5kdFrZ   )r   Zdigest_auth_user)r5   r|   r   methodr   r   r   r   r   rK   r   ZA1ZA2resr   r   r   r     s     &z)HTTPAuthenticator.compute_digest_response)r*   
__module____qualname____doc__r   r   r'   r:   r<   r,   r-   rc   rL   r   rq   rw   rx   ry   rv   r   __classcell__r   r   r8   r   r   z   s$   7
 D" Zr   )r   hashlibr   textwrapr   wsgidavr   r   Zwsgidav.dc.simple_dcr   wsgidav.middlewarer   Zwsgidav.utilr   r	   r
   r   r   r,   rf   __docformat__get_module_loggerr*   r`   r   r   r   r   r   r   <module>   s   J
