Revision: 897 http://trac.macosforge.org/projects/calendarserver/changeset/897 Author: dreid@apple.com Date: 2006-12-22 11:06:59 -0800 (Fri, 22 Dec 2006) Log Message: ----------- first pass at a sudo user directory service Modified Paths: -------------- CalendarServer/branches/users/dreid/sudoers/conf/caldavd-test.plist CalendarServer/branches/users/dreid/sudoers/conf/caldavd.plist CalendarServer/branches/users/dreid/sudoers/twistedcaldav/config.py Added Paths: ----------- CalendarServer/branches/users/dreid/sudoers/conf/sudoers.plist CalendarServer/branches/users/dreid/sudoers/twistedcaldav/directory/sudo.py CalendarServer/branches/users/dreid/sudoers/twistedcaldav/directory/test/sudoers.plist CalendarServer/branches/users/dreid/sudoers/twistedcaldav/directory/test/test_sudo.py Modified: CalendarServer/branches/users/dreid/sudoers/conf/caldavd-test.plist =================================================================== --- CalendarServer/branches/users/dreid/sudoers/conf/caldavd-test.plist 2006-12-22 17:45:36 UTC (rev 896) +++ CalendarServer/branches/users/dreid/sudoers/conf/caldavd-test.plist 2006-12-22 19:06:59 UTC (rev 897) @@ -183,5 +183,9 @@ <array> <string>/principals/user/admin</string> </array> + + + <key>SudoersFile</key> + <string>conf/sudoers.plist</string> </dict> </plist> Modified: CalendarServer/branches/users/dreid/sudoers/conf/caldavd.plist =================================================================== --- CalendarServer/branches/users/dreid/sudoers/conf/caldavd.plist 2006-12-22 17:45:36 UTC (rev 896) +++ CalendarServer/branches/users/dreid/sudoers/conf/caldavd.plist 2006-12-22 19:06:59 UTC (rev 897) @@ -129,5 +129,8 @@ <array> <string>/principals/user/admin</string> </array> + + <key>SudoersFile</key> + <string>/etc/caldavd/sudoers.plist</string> </dict> </plist> Added: CalendarServer/branches/users/dreid/sudoers/conf/sudoers.plist =================================================================== --- CalendarServer/branches/users/dreid/sudoers/conf/sudoers.plist (rev 0) +++ CalendarServer/branches/users/dreid/sudoers/conf/sudoers.plist 2006-12-22 19:06:59 UTC (rev 897) @@ -0,0 +1,34 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> +<key>users</key> +<array> +<!-- Sudo user definitions --> +<!-- + <dict> + <key>authorize-as</key> + <dict> + <key>allow</key> + <true/> + <key>principals</key> + <array> + <string>all</string> + <string>/principals/user/wsanchez</string> + </array> + </dict> + <key>authorize-from</key> + <array> + <string>127.0.0.1</string> + </array> + + <key>username</key> + <string></string> + + <key>password</key> + <string></string> + </dict> +--> +</array> +</dict> +</plist> Modified: CalendarServer/branches/users/dreid/sudoers/twistedcaldav/config.py =================================================================== --- CalendarServer/branches/users/dreid/sudoers/twistedcaldav/config.py 2006-12-22 17:45:36 UTC (rev 896) +++ CalendarServer/branches/users/dreid/sudoers/twistedcaldav/config.py 2006-12-22 19:06:59 UTC (rev 897) @@ -60,7 +60,8 @@ 'ServicePrincipal': '', }, }, - 'AdminPrincipals': ['/principals/user/admin'] + 'AdminPrincipals': ['/principals/user/admin'], + 'SudoersFile': '/etc/caldavd/sudoers.plist', } Added: CalendarServer/branches/users/dreid/sudoers/twistedcaldav/directory/sudo.py =================================================================== --- CalendarServer/branches/users/dreid/sudoers/twistedcaldav/directory/sudo.py (rev 0) +++ CalendarServer/branches/users/dreid/sudoers/twistedcaldav/directory/sudo.py 2006-12-22 19:06:59 UTC (rev 897) @@ -0,0 +1,120 @@ +## +# Copyright (c) 2006 Apple Computer, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# DRI: David reid, dreid@apple.com +## + +""" +Directory service implementation for users who are allowed to authorize +as other principals. +""" + +__all__ = [ + "SudoDirectoryService", +] + +from twisted.python.filepath import FilePath + +from twisted.cred.credentials import (IUsernamePassword, + IUsernameHashedPassword) + +from twistedcaldav.py.plistlib import readPlist +from twistedcaldav.directory.directory import (DirectoryService, + DirectoryRecord, + UnknownRecordTypeError) + +class SudoDirectoryService(DirectoryService): + """ + L{IDirectoryService} implementation for Sudo users. + """ + baseGUID = "1EE00E46-1885-4DBC-A001-590AFA76A8E3" + + realmName = None + + plistFile = None + + recordType = "sudoer" + + def __repr__(self): + return "<%s %r: %r>" % (self.__class__.__name__, self.realmName, + self.plistFile) + + def __init__(self, plistFile): + super(SudoDirectoryService, self).__init__() + + if isinstance(plistFile, (unicode, str)): + plistFile = FilePath(plistFile) + + self.plistFile = plistFile + self._fileInfo = None + self._readPlist() + + def _readPlist(self): + fileInfo = (self.plistFile.getmtime(), self.plistFile.getsize()) + if fileInfo != self._fileInfo: + self._plist = readPlist(self.plistFile.path) + + return self._plist + + def recordTypes(self): + return (self.recordType,) + + def _recordForEntry(self, entry): + return SudoDirectoryRecord( + service=self, + recordType=self.recordType, + shortName=entry['username'], + entry=entry) + + + def listRecords(self, recordType): + if recordType != self.recordType: + raise UnknownRecordTypeError(recordType) + + for entry in self._plist['users']: + yield self._recordForEntry(entry) + + def recordWithShortName(self, recordType, shortName): + if recordType != self.recordType: + raise UnknownRecordTypeError(recordType) + + for entry in self._plist['users']: + if entry['username'] == shortName: + return self._recordForEntry(entry) + + +class SudoDirectoryRecord(DirectoryRecord): + """ + L{DirectoryRecord} implementation for Sudo users. + """ + + def __init__(self, service, recordType, shortName, entry): + super(SudoDirectoryRecord, self).__init__( + service=service, + recordType=recordType, + guid=None, + shortName=shortName, + fullName=shortName, + calendarUserAddresses=set()) + + self.password = entry['password'] + + def verifyCredentials(self, credentials): + if IUsernamePassword.providedBy(credentials): + return credentials.checkPassword(self.password) + elif IUsernameHashedPassword.providedBy(credentials): + return credentials.checkPassword(self.password) + + return super(SudoDirectoryRecord, self).verifyCredentials(credentials) Added: CalendarServer/branches/users/dreid/sudoers/twistedcaldav/directory/test/sudoers.plist =================================================================== --- CalendarServer/branches/users/dreid/sudoers/twistedcaldav/directory/test/sudoers.plist (rev 0) +++ CalendarServer/branches/users/dreid/sudoers/twistedcaldav/directory/test/sudoers.plist 2006-12-22 19:06:59 UTC (rev 897) @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>users</key> + <array> + <dict> + <key>authorize-as</key> + <dict> + <key>allow</key> + <true/> + <key>principals</key> + <array> + <string>all</string> + </array> + </dict> + <key>authorize-from</key> + <array> + <string>127.0.0.1</string> + </array> + <key>password</key> + <string></string> + <key>username</key> + <string></string> + </dict> + </array> +</dict> +</plist> Added: CalendarServer/branches/users/dreid/sudoers/twistedcaldav/directory/test/test_sudo.py =================================================================== --- CalendarServer/branches/users/dreid/sudoers/twistedcaldav/directory/test/test_sudo.py (rev 0) +++ CalendarServer/branches/users/dreid/sudoers/twistedcaldav/directory/test/test_sudo.py 2006-12-22 19:06:59 UTC (rev 897) @@ -0,0 +1,66 @@ +## +# Copyright (c) 2005-2006 Apple Computer, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# DRI: David Reid, dreid@apple.com +## +import os + +from twisted.python.filepath import FilePath + +import twistedcaldav.directory.test.util +from twistedcaldav.directory.sudo import SudoDirectoryService +from twistedcaldav.py.plistlib import writePlist + +plistFile = FilePath(os.path.join(os.path.dirname(__file__), "sudoers.plist")) + +class SudoTestCase( + twistedcaldav.directory.test.util.BasicTestCase, + twistedcaldav.directory.test.util.DigestTestCase +): + """ + Test the Sudo Directory Service + """ + + recordTypes = set(('sudoer',)) + recordType = 'sudoer' + + sudoers = {'alice': {'password': 'alice',}, + } + + def plistFile(self): + if not hasattr(self, "_plistFile"): + self._plistFile = FilePath(self.mktemp()) + plistFile.copyTo(self._plistFile) + return self._plistFile + + def service(self): + service = SudoDirectoryService(self.plistFile()) + service.realmName = "test realm" + return service + + def test_listRecords(self): + for record in self.service().listRecords(self.recordType): + self.failUnless(record.shortName in self.sudoers) + self.assertEqual(self.sudoers[record.shortName]['password'], + record.password) + + def test_recordWithShortName(self): + service = self.service() + + record = service.recordWithShortName('sudoer', 'alice') + self.assertEquals(record.password, 'alice') + + record = service.recordWithShortName('sudoer', 'bob') + self.failIf(record)