[CalendarServer-changes] [1656] CalDAVTester/trunk

source_changes at macosforge.org source_changes at macosforge.org
Mon Jul 9 13:33:14 PDT 2007


Revision: 1656
          http://trac.macosforge.org/projects/calendarserver/changeset/1656
Author:   cdaboo at apple.com
Date:     2007-07-09 13:33:13 -0700 (Mon, 09 Jul 2007)

Log Message:
-----------
Crude digest authentication support.

Modified Paths:
--------------
    CalDAVTester/trunk/scripts/server/serverinfo.dtd
    CalDAVTester/trunk/scripts/server/serverinfo.xml
    CalDAVTester/trunk/src/manager.py
    CalDAVTester/trunk/src/request.py
    CalDAVTester/trunk/src/serverinfo.py
    CalDAVTester/trunk/src/xmlDefs.py

Modified: CalDAVTester/trunk/scripts/server/serverinfo.dtd
===================================================================
--- CalDAVTester/trunk/scripts/server/serverinfo.dtd	2007-07-09 20:31:09 UTC (rev 1655)
+++ CalDAVTester/trunk/scripts/server/serverinfo.dtd	2007-07-09 20:33:13 UTC (rev 1656)
@@ -16,10 +16,11 @@
  DRI: Cyrus Daboo, cdaboo at apple.com
  -->
 
-<!ELEMENT serverinfo (host, port, ssl?, substitutions, serverfilepath)? >
+<!ELEMENT serverinfo (host, port, authtype?, ssl?, substitutions, serverfilepath)? >
 
 	<!ELEMENT host			(#PCDATA)>
 	<!ELEMENT port			(#PCDATA)>
+	<!ELEMENT authtype		(#PCDATA)>
 	<!ELEMENT ssl			EMPTY>
 	<!ELEMENT substitutions	(substitution*)>
 		<!ELEMENT substitution	(key, value)>

Modified: CalDAVTester/trunk/scripts/server/serverinfo.xml
===================================================================
--- CalDAVTester/trunk/scripts/server/serverinfo.xml	2007-07-09 20:31:09 UTC (rev 1655)
+++ CalDAVTester/trunk/scripts/server/serverinfo.xml	2007-07-09 20:33:13 UTC (rev 1656)
@@ -23,6 +23,7 @@
 <serverinfo>
 	<host>localhost</host>
 	<port>8008</port>
+	<authtype>basic</authtype>
 	<substitutions>
 		<substitution>
 			<key>$host:</key>

Modified: CalDAVTester/trunk/src/manager.py
===================================================================
--- CalDAVTester/trunk/src/manager.py	2007-07-09 20:31:09 UTC (rev 1655)
+++ CalDAVTester/trunk/src/manager.py	2007-07-09 20:33:13 UTC (rev 1656)
@@ -40,7 +40,8 @@
     """
     Main class that runs test suites defined in an XML config file.
     """
-    __slots__  = ['server_info', 'populator', 'depopulate', 'tests', 'textMode', 'pid', 'memUsage', 'logLevel']
+    __slots__  = ['server_info', 'populator', 'depopulate', 'tests', 'textMode', 'pid', 'memUsage', 'logLevel', 
+                  'digestCache']
 
     LOG_NONE    = 0
     LOG_LOW     = 1
@@ -56,6 +57,7 @@
         self.pid = 0
         self.memUsage = None
         self.logLevel = level
+        self.digestCache = {}
     
     def log(self, level, str, indent = 0, indentStr = " ", after = 1, before = 0):
         if self.textMode and level <= self.logLevel:

Modified: CalDAVTester/trunk/src/request.py
===================================================================
--- CalDAVTester/trunk/src/request.py	2007-07-09 20:31:09 UTC (rev 1655)
+++ CalDAVTester/trunk/src/request.py	2007-07-09 20:33:13 UTC (rev 1656)
@@ -21,9 +21,108 @@
 """
 
 import base64
+import httplib
+import md5
+import sha
 import src.xmlDefs
 import time
 
+algorithms = {
+    'md5': md5.new,
+    'md5-sess': md5.new,
+    'sha': sha.new,
+}
+
+# DigestCalcHA1
+def calcHA1(
+    pszAlg,
+    pszUserName,
+    pszRealm,
+    pszPassword,
+    pszNonce,
+    pszCNonce,
+    preHA1=None
+):
+    """
+    @param pszAlg: The name of the algorithm to use to calculate the digest.
+        Currently supported are md5 md5-sess and sha.
+
+    @param pszUserName: The username
+    @param pszRealm: The realm
+    @param pszPassword: The password
+    @param pszNonce: The nonce
+    @param pszCNonce: The cnonce
+
+    @param preHA1: If available this is a str containing a previously
+       calculated HA1 as a hex string. If this is given then the values for
+       pszUserName, pszRealm, and pszPassword are ignored.
+    """
+
+    if (preHA1 and (pszUserName or pszRealm or pszPassword)):
+        raise TypeError(("preHA1 is incompatible with the pszUserName, "
+                         "pszRealm, and pszPassword arguments"))
+
+    if preHA1 is None:
+        # We need to calculate the HA1 from the username:realm:password
+        m = algorithms[pszAlg]()
+        m.update(pszUserName)
+        m.update(":")
+        m.update(pszRealm)
+        m.update(":")
+        m.update(pszPassword)
+        HA1 = m.digest()
+    else:
+        # We were given a username:realm:password
+        HA1 = preHA1.decode('hex')
+
+    if pszAlg == "md5-sess":
+        m = algorithms[pszAlg]()
+        m.update(HA1)
+        m.update(":")
+        m.update(pszNonce)
+        m.update(":")
+        m.update(pszCNonce)
+        HA1 = m.digest()
+
+    return HA1.encode('hex')
+
+# DigestCalcResponse
+def calcResponse(
+    HA1,
+    algo,
+    pszNonce,
+    pszNonceCount,
+    pszCNonce,
+    pszQop,
+    pszMethod,
+    pszDigestUri,
+    pszHEntity,
+):
+    m = algorithms[algo]()
+    m.update(pszMethod)
+    m.update(":")
+    m.update(pszDigestUri)
+    if pszQop == "auth-int":
+        m.update(":")
+        m.update(pszHEntity)
+    HA2 = m.digest().encode('hex')
+
+    m = algorithms[algo]()
+    m.update(HA1)
+    m.update(":")
+    m.update(pszNonce)
+    m.update(":")
+    if pszNonceCount and pszCNonce and pszQop:
+        m.update(pszNonceCount)
+        m.update(":")
+        m.update(pszCNonce)
+        m.update(":")
+        m.update(pszQop)
+        m.update(":")
+    m.update(HA2)
+    respHash = m.digest().encode('hex')
+    return respHash
+
 class request( object ):
     """
     Represents the HTTP request to be executed, and verifcation information to
@@ -71,11 +170,14 @@
         
         # Auth
         if self.auth:
-            hdrs["Authorization"] = self.gethttpauth( si )
+            if si.authtype.lower() == "digest":
+                hdrs["Authorization"] = self.gethttpdigestauth( si )
+            else:
+                hdrs["Authorization"] = self.gethttpbasicauth( si )
         
         return hdrs
 
-    def gethttpauth( self, si ):
+    def gethttpbasicauth( self, si ):
         basicauth = [self.user, si.user][self.user == ""]
         basicauth += ":"
         basicauth += [self.pswd, si.pswd][self.pswd == ""]
@@ -83,6 +185,67 @@
         basicauth = basicauth.replace( "\n", "" )
         return basicauth
 
+    def gethttpdigestauth( self, si, wwwauthorize=None ):
+        
+        # Check the nonce cache to see if we've used this user before
+        user = [self.user, si.user][self.user == ""]
+        pswd = [self.pswd, si.pswd][self.pswd == ""]
+        details = None
+        if self.manager.digestCache.has_key(user):
+            details = self.manager.digestCache[user]
+        else:
+            if si.ssl:
+                http = httplib.HTTPSConnection( self.manager.server_info.host, self.manager.server_info.port )
+            else:
+                http = httplib.HTTPConnection( self.manager.server_info.host, self.manager.server_info.port )
+            try:
+                http.request( "OPTIONS", self.getURI(si) )
+            
+                response = http.getresponse()
+    
+            finally:
+                http.close()
+
+            if response.status == 401:
+
+                wwwauthorize = response.msg.getheaders("WWW-Authenticate")
+                for item in wwwauthorize:
+                    if not item.startswith("digest "):
+                        continue
+                    wwwauthorize = item[7:]
+                    def unq(s):
+                        if s[0] == s[-1] == '"':
+                            return s[1:-1]
+                        return s
+                    parts = wwwauthorize.split(',')
+            
+                    details = {}
+        
+                    for (k, v) in [p.split('=', 1) for p in parts]:
+                        details[k.strip()] = unq(v.strip())
+                        
+                    self.manager.digestCache[user] = details
+                    break
+
+        if details:
+            digest = calcResponse(
+                calcHA1(details.get('algorithm'), user, details.get('realm'), pswd, details.get('nonce'), details.get('cnonce')),
+                details.get('algorithm'), details.get('nonce'), details.get('nc'), details.get('cnonce'), details.get('qop'), self.method, self.getURI(si), None
+            )
+    
+            if details.get('qop'):
+                response = ('Digest username="%s", realm="%s", '
+                        'nonce="%s", uri="%s", '
+                        'response=%s, algorithm=%s, cnonce="%s", qop=%s, nc=%s' % (user, details.get('realm'), details.get('nonce'), self.getURI(si), digest, details.get('algorithm'), details.get('cnonce'), details.get('qop'), details.get('nc'), ))
+            else:
+                response = ('Digest username="%s", realm="%s", '
+                        'nonce="%s", uri="%s", '
+                        'response=%s, algorithm=%s' % (user, details.get('realm'), details.get('nonce'), self.getURI(si), digest, details.get('algorithm'), ))
+    
+            return response
+        else:
+            return ""
+
     def getFilePath( self ):
         if self.data != None:
             return self.data.filepath

Modified: CalDAVTester/trunk/src/serverinfo.py
===================================================================
--- CalDAVTester/trunk/src/serverinfo.py	2007-07-09 20:31:09 UTC (rev 1655)
+++ CalDAVTester/trunk/src/serverinfo.py	2007-07-09 20:33:13 UTC (rev 1656)
@@ -26,12 +26,13 @@
     """
     Maintains information about the server beiung targetted.
     """
-    __slots__  = ['host', 'port', 'ssl', 'calendarpath', 'user', 'pswd', 'serverfilepath', 'subsdict', 'extrasubsdict',]
+    __slots__  = ['host', 'port', 'authtype', 'ssl', 'calendarpath', 'user', 'pswd', 'serverfilepath', 'subsdict', 'extrasubsdict',]
 
 
     def __init__( self ):
         self.host = ""
         self.port = 80
+        self.authtype = "basic"
         self.ssl = False
         self.calendarpath = ""
         self.user = ""
@@ -73,6 +74,8 @@
                 self.host = child.firstChild.data.encode("utf-8")
             elif child._get_localName() == src.xmlDefs.ELEMENT_PORT:
                 self.port = int( child.firstChild.data )
+            elif child._get_localName() == src.xmlDefs.ELEMENT_AUTHTYPE:
+                self.authtype = child.firstChild.data.encode("utf-8")
             elif child._get_localName() == src.xmlDefs.ELEMENT_SSL:
                 self.ssl = True
             elif child._get_localName() == src.xmlDefs.ELEMENT_SERVERFILEPATH:

Modified: CalDAVTester/trunk/src/xmlDefs.py
===================================================================
--- CalDAVTester/trunk/src/xmlDefs.py	2007-07-09 20:31:09 UTC (rev 1655)
+++ CalDAVTester/trunk/src/xmlDefs.py	2007-07-09 20:33:13 UTC (rev 1656)
@@ -20,6 +20,7 @@
 
 ELEMENT_ACCOUNT = "account"
 ELEMENT_ARG = "arg"
+ELEMENT_AUTHTYPE = "authtype"
 ELEMENT_BODY = "body"
 ELEMENT_CALDAVTEST = "caldavtest"
 ELEMENT_CALENDARS = "calendars"

-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20070709/73aed198/attachment.html


More information about the calendarserver-changes mailing list