Revision: 1534 http://trac.macosforge.org/projects/calendarserver/changeset/1534 Author: cdaboo@apple.com Date: 2007-05-17 12:29:34 -0700 (Thu, 17 May 2007) Log Message: ----------- Merge of branches/users/cdaboo/better-errors-1318. Modified Paths: -------------- PyOpenDirectory/trunk/src/CDirectoryService.cpp PyOpenDirectory/trunk/src/CDirectoryService.h Added Paths: ----------- PyOpenDirectory/trunk/test_auth.py Modified: PyOpenDirectory/trunk/src/CDirectoryService.cpp =================================================================== --- PyOpenDirectory/trunk/src/CDirectoryService.cpp 2007-05-17 16:56:17 UTC (rev 1533) +++ PyOpenDirectory/trunk/src/CDirectoryService.cpp 2007-05-17 19:29:34 UTC (rev 1534) @@ -31,8 +31,8 @@ extern PyObject* ODException_class; -# define ThrowIfDSErr(x) { long dirStatus = x; if (dirStatus != eDSNoErr) throw dirStatus; } -# define ThrowIfNULL(x) { if (x == NULL) throw -1L; } +# define ThrowIfDSErr(x) { if (x != eDSNoErr) ThrowDSError(x, __FILE__, __LINE__); } +# define ThrowIfNULL(x) { if (x == NULL) ThrowDSError(-1, __FILE__, __LINE__); } // This is copied from WhitePages #define kDSStdRecordTypeResources "dsRecTypeStandard:Resources" @@ -96,9 +96,9 @@ // Get attribute map return _ListAllRecordsWithAttributes(recordType, NULL, attributes); } - catch(long dserror) + catch(SDirectoryServiceException& dserror) { - PyErr_SetObject(ODException_class, Py_BuildValue("((s:i))", "DirectoryServices Error", dserror)); + SetPythonException(dserror); return NULL; } catch(...) @@ -129,9 +129,9 @@ // Get attribute map return _QueryRecordsWithAttributes(attr, value, matchType, NULL, casei, recordType, attributes); } - catch(long dserror) + catch(SDirectoryServiceException& dserror) { - PyErr_SetObject(ODException_class, Py_BuildValue("((s:i))", "DirectoryServices Error", dserror)); + SetPythonException(dserror); return NULL; } catch(...) @@ -160,9 +160,9 @@ // Get attribute map return _QueryRecordsWithAttributes(NULL, NULL, 0, query, casei, recordType, attributes); } - catch(long dserror) + catch(SDirectoryServiceException& dserror) { - PyErr_SetObject(ODException_class, Py_BuildValue("((s:i))", "DirectoryServices Error", dserror)); + SetPythonException(dserror); return NULL; } catch(...) @@ -188,9 +188,9 @@ result = NativeAuthenticationBasic(guid, user, pswd); return true; } - catch(long dserror) + catch(SDirectoryServiceException& dserror) { - PyErr_SetObject(ODException_class, Py_BuildValue("((s:i))", "DirectoryServices Error", dserror)); + SetPythonException(dserror); return false; } catch(...) @@ -216,9 +216,9 @@ result = NativeAuthenticationDigest(guid, user, challenge, response, method); return true; } - catch(long dserror) + catch(SDirectoryServiceException& dserror) { - PyErr_SetObject(ODException_class, Py_BuildValue("((s:i))", "DirectoryServices Error", dserror)); + SetPythonException(dserror); return false; } catch(...) @@ -390,7 +390,7 @@ CloseNode(); CloseService(); } - catch(long dsStatus) + catch(SDirectoryServiceException& dsStatus) { // Cleanup if (context != NULL) @@ -624,7 +624,7 @@ CloseNode(); CloseService(); } - catch(long dsStatus) + catch(SDirectoryServiceException& dsStatus) { // Cleanup if (context != NULL) @@ -800,7 +800,7 @@ long aDataBufSize = sizeof(long) + ::strlen(user) + sizeof(long) + ::strlen(pswd); authData = ::dsDataBufferAllocate(mDir, aDataBufSize); if (authData == NULL) - throw eDSNullDataBuff; + ThrowIfDSErr(eDSNullDataBuff); long aCurLength = 0; long aTempLength = ::strlen(user); ::memcpy(&(authData->fBufferData[aCurLength]), &aTempLength, sizeof(long)); @@ -926,7 +926,7 @@ sizeof(long) + ::strlen(method); authData = ::dsDataBufferAllocate(mDir, aDataBufSize); if (authData == NULL) - throw eDSNullDataBuff; + ThrowIfDSErr(eDSNullDataBuff); long aCurLength = 0; long aTempLength = ::strlen(user); ::memcpy(&(authData->fBufferData[aCurLength]), &aTempLength, sizeof(long)); @@ -1009,7 +1009,7 @@ if (dirStatus != eDSNoErr) { mDir = 0L; - throw dirStatus; + ThrowIfDSErr(dirStatus); } } } @@ -1065,7 +1065,7 @@ else { result = NULL; - throw dirStatus; + ThrowIfDSErr(dirStatus); } dirStatus = ::dsDataListDeallocate(mDir, nodePath); free(nodePath); @@ -1110,7 +1110,7 @@ mData = ::dsDataBufferAllocate(mDir, cBufferSize); if (mData == NULL) { - throw eDSNullDataBuff; + ThrowIfDSErr(eDSNullDataBuff); } mDataSize = cBufferSize; } @@ -1139,7 +1139,7 @@ mData = ::dsDataBufferAllocate(mDir, 2 * mDataSize); if (mData == NULL) { - throw eDSNullDataBuff; + ThrowIfDSErr(eDSNullDataBuff); } mDataSize *= 2; } @@ -1184,3 +1184,19 @@ result[len] = 0; return result; } + +void CDirectoryService::ThrowDSError(long error, const char* file, long line) +{ + CDirectoryService::SDirectoryServiceException dirStatus; + dirStatus.mDSError = error; + ::snprintf(dirStatus.mDescription, 1024, "Exception raised in file %s at line %ld", file, line); + throw dirStatus; +} + +void CDirectoryService::SetPythonException(const SDirectoryServiceException& ex) +{ + char error[1024]; + ::snprintf(error, 1024, "%s %s", "DirectoryServices Error:", ex.mDescription); + PyErr_SetObject(ODException_class, Py_BuildValue("((s:i))", error, ex.mDSError)); +} + Modified: PyOpenDirectory/trunk/src/CDirectoryService.h =================================================================== --- PyOpenDirectory/trunk/src/CDirectoryService.h 2007-05-17 16:56:17 UTC (rev 1533) +++ PyOpenDirectory/trunk/src/CDirectoryService.h 2007-05-17 19:29:34 UTC (rev 1534) @@ -40,6 +40,13 @@ bool AuthenticateUserDigest(const char* guid, const char* user, const char* challenge, const char* response, const char* method, bool& result); private: + + struct SDirectoryServiceException + { + long mDSError; + char mDescription[1024]; + }; + const char* mNodeName; tDirReference mDir; tDirNodeReference mNode; @@ -70,4 +77,7 @@ char* CStringFromBuffer(tDataBufferPtr data); char* CStringFromData(const char* data, size_t len); + + void ThrowDSError(long error, const char* file, long line); + void SetPythonException(const SDirectoryServiceException& ex); }; Copied: PyOpenDirectory/trunk/test_auth.py (from rev 1533, PyOpenDirectory/branches/users/cdaboo/better-errors-1318/test_auth.py) =================================================================== --- PyOpenDirectory/trunk/test_auth.py (rev 0) +++ PyOpenDirectory/trunk/test_auth.py 2007-05-17 19:29:34 UTC (rev 1534) @@ -0,0 +1,201 @@ +#!/usr/bin/env python + +from getpass import getpass +import opendirectory +import dsattributes +import md5 +import sha + +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 + + +attempts = 100 + +realm = "/Search" +nonce = "128446648710842461101646794502" +nc = "00000001" +cnonce = "0a4f113b12345" +uri = "/principals/" +method = "GET" + +def doAuthDigest(username, password, qop, algorithm): + failures = 0 + + result = opendirectory.queryRecordsWithAttribute( + od, + dsattributes.kDSNAttrRecordName, + username, + dsattributes.eDSExact, + False, + dsattributes.kDSStdRecordTypeUsers, + [dsattributes.kDS1AttrGeneratedUID]) + guid = result[username][dsattributes.kDS1AttrGeneratedUID] + + expected = calcResponse( + calcHA1(algorithm, username, realm, password, nonce, cnonce), + algorithm, nonce, nc, cnonce, qop, method, uri, None + ) + #print expected + + if qop: + challenge = 'Digest realm="%s", nonce="%s", algorithm=%s, qop="%s"' % (realm, nonce, algorithm, qop,) + else: + challenge = 'Digest realm="%s", nonce="%s", algorithm=%s' % (realm, nonce, algorithm,) + if qop: + response = ('Digest username="%s", realm="%s", ' + 'nonce="%s", digest-uri="%s", ' + 'response=%s, algorithm=%s, cnonce="%s", qop=%s, nc=%s' % (username, realm, nonce, uri, expected, algorithm, cnonce, qop, nc, )) + else: + response = ('Digest username="%s", realm="%s", ' + 'nonce="%s", digest-uri="%s", ' + 'response=%s, algorithm=%s' % (username, realm, nonce, uri, expected, algorithm, )) + + print " Challenge: %s" % (challenge,) + print " Response: %s" % (response, ) + + for x in xrange(attempts): + success = opendirectory.authenticateUserDigest( + od, + guid, + username, + challenge, + response, + method + ) + + if not success: + failures += 1 + + print "\n%d failures out of %d attempts for Digest.\n\n" % (failures, attempts) + +def doAuthBasic(username, password): + failures = 0 + + result = opendirectory.queryRecordsWithAttribute( + od, + dsattributes.kDSNAttrRecordName, + username, + dsattributes.eDSExact, + False, + dsattributes.kDSStdRecordTypeUsers, + [dsattributes.kDS1AttrGeneratedUID]) + guid = result[username][dsattributes.kDS1AttrGeneratedUID] + + for x in xrange(attempts): + success = opendirectory.authenticateUserBasic( + od, + guid, + username, + password, + ) + + if not success: + failures += 1 + + print "\n%d failures out of %d attempts for Basic.\n\n" % (failures, attempts) + +search = raw_input("DS search path: ") +user = raw_input("User: ") +pswd = getpass("Password: ") + +od = opendirectory.odInit(search) + +doAuthBasic(user, pswd) +doAuthDigest(user, pswd, None, "md5") +#doAuth(user, pswd, "auth", "md5") +#doAuth(user, pswd, "auth", "md5-sess") +