Package caldavclientlibrary :: Package protocol :: Package http :: Package authentication :: Module digest
[hide private]
[frames] | no frames]

Source Code for Module caldavclientlibrary.protocol.http.authentication.digest

  1  ## 
  2  # Copyright (c) 2006-2016 Apple Inc. All rights reserved. 
  3  # 
  4  # Licensed under the Apache License, Version 2.0 (the "License"); 
  5  # you may not use this file except in compliance with the License. 
  6  # You may obtain a copy of the License at 
  7  # 
  8  # http://www.apache.org/licenses/LICENSE-2.0 
  9  # 
 10  # Unless required by applicable law or agreed to in writing, software 
 11  # distributed under the License is distributed on an "AS IS" BASIS, 
 12  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 13  # See the License for the specific language governing permissions and 
 14  # limitations under the License. 
 15  ## 
 16   
 17  from caldavclientlibrary.protocol.http.authentication.authenticator import Authenticator 
 18  from caldavclientlibrary.protocol.http.util import parsetoken 
 19  from caldavclientlibrary.protocol.http.definitions import headers 
 20  from StringIO import StringIO 
 21  import hashlib 
22 23 -class Digest(Authenticator):
24
25 - def __init__(self, user, pswd, www_authenticate):
26 self.fields = {} 27 self.fields['username'] = user 28 self.fields['password'] = pswd 29 self.parseAuthenticateHeader(www_authenticate) 30 self.stale = False 31 self.clientCount = 0
32
33 - def setDetails(self, user, pswd, www_authenticate):
34 self.fields['username'] = user 35 self.fields['password'] = pswd 36 self.parseAuthenticateHeader(www_authenticate)
37
38 - def addHeaders(self, hdrs, request):
39 # Generate response 40 self.generateResponse(request) 41 42 # Generate header 43 os = StringIO() 44 os.write("Digest username=\"%s\"," % (self.fields['username'],)) 45 os.write(" realm=\"%s\"," % (self.fields['realm'],)) 46 os.write(" nonce=\"%s\"," % (self.fields['nonce'],)) 47 os.write(" uri=\"%s\"," % (request.getURL(),)) 48 if "qop" in self.fields: 49 os.write(" qop=auth,") 50 os.write(" nc=\"%s\"" % (self.fields['nc'],)) 51 os.write(" cnonce=\"%s\"" % (self.fields['cnonce'],)) 52 os.write(" response=\"%s\"" % (self.response,)) 53 54 if "algorithm" in self.fields: 55 os.write(", algorithm=\"%s\"" % (self.fields['algorithm'],)) 56 if "opaque" in self.fields: 57 os.write(", opaque=\"%s\"" % (self.fields['opaque'],)) 58 59 hdrs.append((headers.Authorization, os.getvalue()))
60
61 - def parseAuthenticateHeader(self, hdrs):
62 for hdr in hdrs: 63 64 # Strip any space 65 hdr = hdr.strip() 66 67 # Must have Digest token 68 if hdr[:7].lower() != "digest ": 69 continue 70 else: 71 hdr = hdr[7:] 72 73 # Get each name/value pair 74 while True: 75 name, hdr = parsetoken(hdr, " \t=") 76 77 if not name or not hdr: 78 return 79 name = name.lower() 80 81 value, hdr = parsetoken(hdr, ", ") 82 if not value: 83 return 84 85 if name in ("realm", "domain", "nonce", "opaque", "algorithm", "qop"): 86 self.fields[name] = value 87 88 elif name == "stale": 89 self.stale = (value.lower() != "false") 90 91 else: 92 # Unknown token - ignore 93 pass 94 95 # Punt over space 96 hdr = hdr.strip() 97 98 break
99 100 algorithms = { 101 'md5': hashlib.md5, 102 'md5-sess': hashlib.md5, 103 'sha': hashlib.sha1, 104 } 105 106 # DigestCalcHA1 107 @staticmethod
108 - def calcHA1( 109 pszAlg, 110 pszUserName, 111 pszRealm, 112 pszPassword, 113 pszNonce, 114 pszCNonce, 115 preHA1=None 116 ):
117 """ 118 @param pszAlg: The name of the algorithm to use to calculate the Digest. 119 Currently supported are md5 md5-sess and sha. 120 121 @param pszUserName: The username 122 @param pszRealm: The realm 123 @param pszPassword: The password 124 @param pszNonce: The nonce 125 @param pszCNonce: The cnonce 126 127 @param preHA1: If available this is a str containing a previously 128 calculated HA1 as a hex string. If this is given then the values for 129 pszUserName, pszRealm, and pszPassword are ignored. 130 """ 131 132 if (preHA1 and (pszUserName or pszRealm or pszPassword)): 133 raise TypeError(("preHA1 is incompatible with the pszUserName, " 134 "pszRealm, and pszPassword arguments")) 135 136 if preHA1 is None: 137 # We need to calculate the HA1 from the username:realm:password 138 m = Digest.algorithms[pszAlg]() 139 m.update(pszUserName) 140 m.update(":") 141 m.update(pszRealm) 142 m.update(":") 143 m.update(pszPassword) 144 HA1 = m.digest() 145 else: 146 # We were given a username:realm:password 147 HA1 = preHA1.decode('hex') 148 149 if pszAlg == "md5-sess": 150 m = Digest.algorithms[pszAlg]() 151 m.update(HA1) 152 m.update(":") 153 m.update(pszNonce) 154 m.update(":") 155 m.update(pszCNonce) 156 HA1 = m.digest() 157 158 return HA1.encode('hex')
159 160 # DigestCalcResponse 161 @staticmethod
162 - def calcResponse( 163 HA1, 164 algo, 165 pszNonce, 166 pszNonceCount, 167 pszCNonce, 168 pszQop, 169 pszMethod, 170 pszDigestUri, 171 pszHEntity, 172 ):
173 m = Digest.algorithms[algo]() 174 m.update(pszMethod) 175 m.update(":") 176 m.update(pszDigestUri) 177 if pszQop == "auth-int": 178 m.update(":") 179 m.update(pszHEntity) 180 HA2 = m.digest().encode('hex') 181 182 m = Digest.algorithms[algo]() 183 m.update(HA1) 184 m.update(":") 185 m.update(pszNonce) 186 m.update(":") 187 if pszNonceCount and pszCNonce: # pszQop: 188 m.update(pszNonceCount) 189 m.update(":") 190 m.update(pszCNonce) 191 m.update(":") 192 m.update(pszQop) 193 m.update(":") 194 m.update(HA2) 195 respHash = m.digest().encode('hex') 196 return respHash
197
198 - def generateResponse(self, request):
199 self.response = Digest.calcResponse( 200 Digest.calcHA1( 201 self.fields.get("algorithm", "md5"), 202 self.fields.get("username", ""), 203 self.fields.get("realm", ""), 204 self.fields.get("password", ""), 205 self.fields.get("nonce", ""), 206 self.fields.get("cnonce", ""), 207 ), 208 self.fields.get("algorithm", "md5"), 209 self.fields.get("nonce", ""), 210 self.fields.get("nc", ""), 211 self.fields.get("cnonce", ""), 212 self.fields.get("qop", ""), 213 request.method, 214 request.url, 215 None, 216 )
217