NovaBryan/EVE_Dump
Log | Files | 📦 下载最新源码 (.zip) | Refs | README

auth_digest.py (7628B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\carbon\common\lib\cherrypy\lib\auth_digest.py
      2 """An implementation of the server-side of HTTP Digest Access
      3 Authentication, which is described in :rfc:`2617`.
      4 
      5 Example usage, using the built-in get_ha1_dict_plain function which uses a dict
      6 of plaintext passwords as the credentials store::
      7 
      8     userpassdict = {'alice' : '4x5istwelve'}
      9     get_ha1 = cherrypy.lib.auth_digest.get_ha1_dict_plain(userpassdict)
     10     digest_auth = {'tools.auth_digest.on': True,
     11                    'tools.auth_digest.realm': 'wonderland',
     12                    'tools.auth_digest.get_ha1': get_ha1,
     13                    'tools.auth_digest.key': 'a565c27146791cfb',
     14     }
     15     app_config = { '/' : digest_auth }
     16 """
     17 __author__ = 'visteya'
     18 __date__ = 'April 2009'
     19 import time
     20 from cherrypy._cpcompat import parse_http_list, parse_keqv_list
     21 import cherrypy
     22 from cherrypy._cpcompat import md5, ntob
     23 md5_hex = lambda s: md5(ntob(s)).hexdigest()
     24 qop_auth = 'auth'
     25 qop_auth_int = 'auth-int'
     26 valid_qops = (qop_auth, qop_auth_int)
     27 valid_algorithms = ('MD5', 'MD5-sess')
     28 
     29 def TRACE(msg):
     30     cherrypy.log(msg, context='TOOLS.AUTH_DIGEST')
     31 
     32 
     33 def get_ha1_dict_plain(user_password_dict):
     34 
     35     def get_ha1(realm, username):
     36         password = user_password_dict.get(username)
     37         if password:
     38             return md5_hex('%s:%s:%s' % (username, realm, password))
     39 
     40     return get_ha1
     41 
     42 
     43 def get_ha1_dict(user_ha1_dict):
     44 
     45     def get_ha1(realm, username):
     46         return user_ha1_dict.get(user)
     47 
     48     return get_ha1
     49 
     50 
     51 def get_ha1_file_htdigest(filename):
     52 
     53     def get_ha1(realm, username):
     54         result = None
     55         f = open(filename, 'r')
     56         for line in f:
     57             u, r, ha1 = line.rstrip().split(':')
     58             if u == username and r == realm:
     59                 result = ha1
     60                 break
     61 
     62         f.close()
     63         return result
     64 
     65     return get_ha1
     66 
     67 
     68 def synthesize_nonce(s, key, timestamp = None):
     69     if timestamp is None:
     70         timestamp = int(time.time())
     71     h = md5_hex('%s:%s:%s' % (timestamp, s, key))
     72     nonce = '%s:%s' % (timestamp, h)
     73     return nonce
     74 
     75 
     76 def H(s):
     77     return md5_hex(s)
     78 
     79 
     80 class HttpDigestAuthorization(object):
     81 
     82     def errmsg(self, s):
     83         return 'Digest Authorization header: %s' % s
     84 
     85     def __init__(self, auth_header, http_method, debug = False):
     86         self.http_method = http_method
     87         self.debug = debug
     88         scheme, params = auth_header.split(' ', 1)
     89         self.scheme = scheme.lower()
     90         if self.scheme != 'digest':
     91             raise ValueError('Authorization scheme is not "Digest"')
     92         self.auth_header = auth_header
     93         items = parse_http_list(params)
     94         paramsd = parse_keqv_list(items)
     95         self.realm = paramsd.get('realm')
     96         self.username = paramsd.get('username')
     97         self.nonce = paramsd.get('nonce')
     98         self.uri = paramsd.get('uri')
     99         self.method = paramsd.get('method')
    100         self.response = paramsd.get('response')
    101         self.algorithm = paramsd.get('algorithm', 'MD5')
    102         self.cnonce = paramsd.get('cnonce')
    103         self.opaque = paramsd.get('opaque')
    104         self.qop = paramsd.get('qop')
    105         self.nc = paramsd.get('nc')
    106         if self.algorithm not in valid_algorithms:
    107             raise ValueError(self.errmsg("Unsupported value for algorithm: '%s'" % self.algorithm))
    108         has_reqd = self.username and self.realm and self.nonce and self.uri and self.response
    109         if not has_reqd:
    110             raise ValueError(self.errmsg('Not all required parameters are present.'))
    111         if self.qop:
    112             if self.qop not in valid_qops:
    113                 raise ValueError(self.errmsg("Unsupported value for qop: '%s'" % self.qop))
    114             if not (self.cnonce and self.nc):
    115                 raise ValueError(self.errmsg('If qop is sent then cnonce and nc MUST be present'))
    116         elif self.cnonce or self.nc:
    117             raise ValueError(self.errmsg('If qop is not sent, neither cnonce nor nc can be present'))
    118 
    119     def __str__(self):
    120         return 'authorization : %s' % self.auth_header
    121 
    122     def validate_nonce(self, s, key):
    123         try:
    124             timestamp, hashpart = self.nonce.split(':', 1)
    125             s_timestamp, s_hashpart = synthesize_nonce(s, key, timestamp).split(':', 1)
    126             is_valid = s_hashpart == hashpart
    127             if self.debug:
    128                 TRACE('validate_nonce: %s' % is_valid)
    129             return is_valid
    130         except ValueError:
    131             pass
    132 
    133         return False
    134 
    135     def is_nonce_stale(self, max_age_seconds = 600):
    136         try:
    137             timestamp, hashpart = self.nonce.split(':', 1)
    138             if int(timestamp) + max_age_seconds > int(time.time()):
    139                 return False
    140         except ValueError:
    141             pass
    142 
    143         if self.debug:
    144             TRACE('nonce is stale')
    145         return True
    146 
    147     def HA2(self, entity_body = ''):
    148         if self.qop is None or self.qop == 'auth':
    149             a2 = '%s:%s' % (self.http_method, self.uri)
    150         elif self.qop == 'auth-int':
    151             a2 = '%s:%s:%s' % (self.http_method, self.uri, H(entity_body))
    152         else:
    153             raise ValueError(self.errmsg('Unrecognized value for qop!'))
    154         return H(a2)
    155 
    156     def request_digest(self, ha1, entity_body = ''):
    157         ha2 = self.HA2(entity_body)
    158         if self.qop:
    159             req = '%s:%s:%s:%s:%s' % (self.nonce,
    160              self.nc,
    161              self.cnonce,
    162              self.qop,
    163              ha2)
    164         else:
    165             req = '%s:%s' % (self.nonce, ha2)
    166         if self.algorithm == 'MD5-sess':
    167             ha1 = H('%s:%s:%s' % (ha1, self.nonce, self.cnonce))
    168         digest = H('%s:%s' % (ha1, req))
    169         return digest
    170 
    171 
    172 def www_authenticate(realm, key, algorithm = 'MD5', nonce = None, qop = qop_auth, stale = False):
    173     if qop not in valid_qops:
    174         raise ValueError("Unsupported value for qop: '%s'" % qop)
    175     if algorithm not in valid_algorithms:
    176         raise ValueError("Unsupported value for algorithm: '%s'" % algorithm)
    177     if nonce is None:
    178         nonce = synthesize_nonce(realm, key)
    179     s = 'Digest realm="%s", nonce="%s", algorithm="%s", qop="%s"' % (realm,
    180      nonce,
    181      algorithm,
    182      qop)
    183     if stale:
    184         s += ', stale="true"'
    185     return s
    186 
    187 
    188 def digest_auth(realm, get_ha1, key, debug = False):
    189     request = cherrypy.serving.request
    190     auth_header = request.headers.get('authorization')
    191     nonce_is_stale = False
    192     if auth_header is not None:
    193         try:
    194             auth = HttpDigestAuthorization(auth_header, request.method, debug=debug)
    195         except ValueError:
    196             raise cherrypy.HTTPError(400, 'The Authorization header could not be parsed.')
    197 
    198         if debug:
    199             TRACE(str(auth))
    200         if auth.validate_nonce(realm, key):
    201             ha1 = get_ha1(realm, auth.username)
    202             if ha1 is not None:
    203                 digest = auth.request_digest(ha1, entity_body=request.body)
    204                 if digest == auth.response:
    205                     if debug:
    206                         TRACE('digest matches auth.response')
    207                     nonce_is_stale = auth.is_nonce_stale(max_age_seconds=600)
    208                     if not nonce_is_stale:
    209                         request.login = auth.username
    210                         if debug:
    211                             TRACE('authentication of %s successful' % auth.username)
    212                         return
    213     header = www_authenticate(realm, key, stale=nonce_is_stale)
    214     if debug:
    215         TRACE(header)
    216     cherrypy.serving.response.headers['WWW-Authenticate'] = header
    217     raise cherrypy.HTTPError(401, 'You are not authorized to access that resource')