def test_mergeData(self): """ Verify we don't lose keys which are present in the old but not replaced in the new. """ old = ConfigDict({ "Scheduling": ConfigDict({ "iMIP": ConfigDict({ "Enabled": True, "Receiving": ConfigDict({ "Username": "******", "Server": "example.com", }), "Sending": ConfigDict({ "Username": "******", }), "AddressPatterns": ["mailto:.*"], }), }), }) new = ConfigDict({ "Scheduling": ConfigDict({ "iMIP": ConfigDict({ "Enabled": False, "Receiving": ConfigDict({ "Username": "******", }), }), }), }) mergeData(old, new) self.assertEquals(old.Scheduling.iMIP.Receiving.Server, "example.com") self.assertEquals(old.Scheduling.iMIP.Sending.Username, "plugh")
def read(self): """ Reads in the data contained in the writable plist file. @return: C{ConfigDict} """ if os.path.exists(self.fileName): self.currentConfigSubset = ConfigDict( mapping=plistlib.readPlist(self.fileName)) else: self.currentConfigSubset = ConfigDict()
def test_matchClientFixes(self): """ Test that L{matchClientFixes} correctly identifies clients with matching fix tokens. """ c = ConfigDict() c.ClientFixes = { "fix1": [ "Client/1\\.0.*", "Client/1\\.1(\\..*)?", "Client/2", ], "fix2": [ "Other/1\\.0.*", ], } _updateClientFixes(c) _updateClientFixes(c) # Valid matches for ua in ( "Client/1.0 FooBar/2", "Client/1.0.1 FooBar/2", "Client/1.0.1.1 FooBar/2", "Client/1.1 FooBar/2", "Client/1.1.1 FooBar/2", "Client/2 FooBar/2", ): self.assertEqual( matchClientFixes(c, ua), set(("fix1", )), msg="Did not match {}".format(ua), ) # Valid non-matches for ua in ( "Client/1 FooBar/2", "Client/1.10 FooBar/2", "Client/2.0 FooBar/2", "Client/2.0.1 FooBar/2", "Client FooBar/2", "Client/3 FooBar/2", "Client/3.0 FooBar/2", "Client/10 FooBar/2", "Client/10.0 FooBar/2", "Client/10.0.1 FooBar/2", "Client/10.0.1 (Client/1.0) FooBar/2", "Client/10.0.1 (foo Client/1.0 bar) FooBar/2", ): self.assertEqual( matchClientFixes(c, ua), set(), msg="Incorrectly matched {}".format(ua), )
def test_matchClientFixes(self): """ Test that L{matchClientFixes} correctly identifies clients with matching fix tokens. """ c = ConfigDict() c.ClientFixes = { "fix1": [ "Client/1\\.0.*", "Client/1\\.1(\\..*)?", "Client/2", ], "fix2": [ "Other/1\\.0.*", ], } _updateClientFixes(c) _updateClientFixes(c) # Valid matches for ua in ( "Client/1.0 FooBar/2", "Client/1.0.1 FooBar/2", "Client/1.0.1.1 FooBar/2", "Client/1.1 FooBar/2", "Client/1.1.1 FooBar/2", "Client/2 FooBar/2", ): self.assertEqual( matchClientFixes(c, ua), set(("fix1",)), msg="Did not match {}".format(ua), ) # Valid non-matches for ua in ( "Client/1 FooBar/2", "Client/1.10 FooBar/2", "Client/2.0 FooBar/2", "Client/2.0.1 FooBar/2", "Client FooBar/2", "Client/3 FooBar/2", "Client/3.0 FooBar/2", "Client/10 FooBar/2", "Client/10.0 FooBar/2", "Client/10.0.1 FooBar/2", "Client/10.0.1 (Client/1.0) FooBar/2", "Client/10.0.1 (foo Client/1.0 bar) FooBar/2", ): self.assertEqual( matchClientFixes(c, ua), set(), msg="Incorrectly matched {}".format(ua), )
def test_ConfigDict(self): configDict = ConfigDict({ "a": "A", "b": "B", "c": "C", }) # Test either syntax inbound configDict["d"] = "D" configDict.e = "E" # Test either syntax outbound for key in "abcde": value = key.upper() self.assertEquals(configDict[key], value) self.assertEquals(configDict.get(key), value) self.assertEquals(getattr(configDict, key), value) self.assertIn(key, configDict) self.assertTrue(hasattr(configDict, key)) self.assertEquals(configDict.a, "A") self.assertEquals(configDict.d, "D") self.assertEquals(configDict.e, "E") # Test either syntax for delete del configDict["d"] delattr(configDict, "e") # Test either syntax for absence for key in "de": self.assertNotIn(key, configDict) self.assertFalse(hasattr(configDict, key)) self.assertRaises(KeyError, lambda: configDict[key]) self.assertRaises(AttributeError, getattr, configDict, key) self.assertRaises(AttributeError, lambda: configDict.e) self.assertRaises(AttributeError, lambda: configDict.f) # Keys may not begin with "_" in dict syntax def set(): configDict["_x"] = "X" self.assertRaises(KeyError, set) # But attr syntax is OK configDict._x = "X" self.assertEquals(configDict._x, "X")
def test_overridesConfig(self): """ Test that values on the command line's -o and --option options overide the config file """ myConfig = ConfigDict(DEFAULT_CONFIG) myConfigFile = self.mktemp() writePlist(myConfig, myConfigFile) argv = [ "-f", myConfigFile, "-o", "EnableSACLs", "-o", "HTTPPort=80", "-o", "BindAddresses=127.0.0.1,127.0.0.2,127.0.0.3", "-o", "DocumentRoot=/dev/null", "-o", "UserName=None", "-o", "EnableProxyPrincipals=False", ] self.config.parseOptions(argv) self.assertEquals(config.EnableSACLs, True) self.assertEquals(config.HTTPPort, 80) self.assertEquals(config.BindAddresses, ["127.0.0.1", "127.0.0.2", "127.0.0.3"]) self.assertEquals(config.DocumentRoot, "/dev/null") self.assertEquals(config.UserName, None) self.assertEquals(config.EnableProxyPrincipals, False) argv = ["-o", "Authentication=This Doesn't Matter"] self.assertRaises(UsageError, self.config.parseOptions, argv)
def test_missingCertificate(self): success, _ignore_reason = verifyTLSCertificate( ConfigDict({ "SSLCertificate": "missing", "SSLKeychainIdentity": "missing", })) self.assertFalse(success)
def test_updates(self): content = """<plist version="1.0"> <dict> <key>key1</key> <string>before</string> <key>key2</key> <integer>10</integer> </dict> </plist>""" self.fp.setContent(PREAMBLE + content) config = ConfigDict() writable = WritableConfig(config, self.configFile) writable.read() writable.set({"key1": "after"}) writable.set({"key2": 15}) writable.set({"key2": 20}) # override previous set writable.set({"key3": ["a", "b", "c"]}) self.assertEquals(writable.currentConfigSubset, { "key1": "after", "key2": 20, "key3": ["a", "b", "c"] }) writable.save() writable2 = WritableConfig(config, self.configFile) writable2.read() self.assertEquals(writable2.currentConfigSubset, { "key1": "after", "key2": 20, "key3": ["a", "b", "c"] })
def test_getPubSubAPSConfiguration(self): config = ConfigDict({ "EnableSSL" : True, "ServerHostName" : "calendars.example.com", "SSLPort" : 8443, "HTTPPort" : 8008, "Notifications" : { "Services" : { "APNS" : { "CalDAV" : { "Topic" : "test topic", }, "SubscriptionRefreshIntervalSeconds" : 42, "SubscriptionURL" : "apns", "Environment" : "prod", "Enabled" : True, }, }, }, }) result = getPubSubAPSConfiguration(("CalDAV", "foo",), config) self.assertEquals( result, { "SubscriptionRefreshIntervalSeconds": 42, "SubscriptionURL": "https://calendars.example.com:8443/apns", "APSBundleID": "test topic", "APSEnvironment": "prod" } )
def test_emptyCertificate(self): certFilePath = FilePath(self.mktemp()) certFilePath.setContent("") success, _ignore_reason = verifyTLSCertificate( ConfigDict({ "SSLCertificate": certFilePath.path, })) self.assertFalse(success)
def test_getLanguage(self): """ Test that getLanguage( ) examines config. """ self.assertEquals( getLanguage(ConfigDict({"Localization": { "Language": "xyzzy" }})), "xyzzy")
def test_specifyConfigFile(self): """ Test that specifying a config file from the command line loads the global config with those values properly. """ myConfig = ConfigDict(DEFAULT_CONFIG) myConfig.Authentication.Basic.Enabled = False myConfig.HTTPPort = 80 myConfig.ServerHostName = "calendar.calenderserver.org" myConfigFile = self.mktemp() writePlist(myConfig, myConfigFile) args = ["-f", myConfigFile] self.config.parseOptions(args) self.assertEquals(config.ServerHostName, myConfig["ServerHostName"]) self.assertEquals(config.HTTPPort, myConfig.HTTPPort) self.assertEquals(config.Authentication.Basic.Enabled, myConfig.Authentication.Basic.Enabled)
def setUp(self): TestCase.setUp(self) self.options = TestCalDAVOptions() self.options.parent = Options() self.options.parent["gid"] = None self.options.parent["uid"] = None self.options.parent["nodaemon"] = None self.config = ConfigDict(DEFAULT_CONFIG) accountsFile = os.path.join(sourceRoot, "twistedcaldav/directory/test/accounts.xml") resourcesFile = os.path.join(sourceRoot, "twistedcaldav/directory/test/resources.xml") augmentsFile = os.path.join(sourceRoot, "twistedcaldav/directory/test/augments.xml") pemFile = os.path.join(sourceRoot, "twistedcaldav/test/data/server.pem") self.config["DirectoryService"] = { "params": {"xmlFile": accountsFile}, "type": "twistedcaldav.directory.xmlfile.XMLDirectoryService" } self.config["ResourceService"] = { "params": {"xmlFile": resourcesFile}, } self.config["AugmentService"] = { "params": {"xmlFiles": [augmentsFile]}, "type": "twistedcaldav.directory.augment.AugmentXMLDB" } self.config.UseDatabase = False self.config.ServerRoot = self.mktemp() self.config.ConfigRoot = "config" self.config.ProcessType = "Single" self.config.SSLPrivateKey = pemFile self.config.SSLCertificate = pemFile self.config.EnableSSL = True self.config.Memcached.Pools.Default.ClientEnabled = False self.config.Memcached.Pools.Default.ServerEnabled = False self.config.DirectoryAddressBook.Enabled = False self.config.SudoersFile = "" if self.configOptions: self.config.update(self.configOptions) os.mkdir(self.config.ServerRoot) os.mkdir(os.path.join(self.config.ServerRoot, self.config.DocumentRoot)) os.mkdir(os.path.join(self.config.ServerRoot, self.config.DataRoot)) os.mkdir(os.path.join(self.config.ServerRoot, self.config.ConfigRoot)) self.configFile = self.mktemp() self.writeConfig()
def set(self, data): """ Merges data into a ConfigDict of changes intended to be saved to disk when save( ) is called. @param data: a dict containing new values @type data: C{dict} """ if not isinstance(data, ConfigDict): data = ConfigDict(mapping=data) mergeData(self.currentConfigSubset, data) self.dirty = True
def __init__(self, wrappedConfig, fileName): """ @param wrappedConfig: the Config object to read from @type wrappedConfig: C{Config} @param fileName: the full path to the modifiable plist @type fileName: C{str} """ self.config = wrappedConfig self.fileName = fileName self.changes = None self.currentConfigSubset = ConfigDict() self.dirty = False
def test_readSuccessful(self): content = """<plist version="1.0"> <dict> <key>string</key> <string>foo</string> </dict> </plist>""" self.fp.setContent(PREAMBLE + content) config = ConfigDict() writable = WritableConfig(config, self.configFile) writable.read() self.assertEquals(writable.currentConfigSubset, {"string": "foo"})
def test_bogusCertificate(self): certFilePath = FilePath(self.mktemp()) certFilePath.setContent("bogus") keyFilePath = FilePath(self.mktemp()) keyFilePath.setContent("bogus") success, _ignore_reason = verifyTLSCertificate( ConfigDict({ "SSLCertificate": certFilePath.path, "SSLPrivateKey": keyFilePath.path, "SSLAuthorityChain": "", "SSLMethod": "SSLv3_METHOD", "SSLCiphers": "ALL:!aNULL:!ADH:!eNULL:!LOW:!EXP:RC4+RSA:+HIGH:+MEDIUM", "SSLKeychainIdentity": "missing", })) self.assertFalse(success)
def test_specifyDictPath(self): """ Test that we can specify command line overrides to leafs using a "/" seperated path. Such as "-o MultiProcess/ProcessCount=1" """ myConfig = ConfigDict(DEFAULT_CONFIG) myConfigFile = self.mktemp() writePlist(myConfig, myConfigFile) argv = [ "-o", "MultiProcess/ProcessCount=102", "-f", myConfigFile, ] self.config.parseOptions(argv) self.assertEquals(config.MultiProcess["ProcessCount"], 102)
def command_writeConfig(self, command): """ Write config to secondary, writable plist @param command: the dictionary parsed from the plist read from stdin @type command: C{dict} """ writable = WritableConfig(config, config.WritableConfigFile) writable.read() valuesToWrite = command.get("Values", {}) # Note: values are unicode if they contain non-ascii for keyPath, value in flattenDictionary(valuesToWrite): if keyPath in WRITABLE_CONFIG_KEYS: writable.set(setKeyPath(ConfigDict(), keyPath, value)) try: writable.save(restart=False) except Exception, e: self.respond(command, {"error": str(e)})
def test_processLocalizationFiles(self): """ Make sure that on OS X the .lproj files are converted properly. """ if sys.platform == "darwin": tmpdir = self.mktemp() settings = ConfigDict({ "TranslationsDirectory": os.path.join(os.path.dirname(__file__), "data", "translations"), "LocalesDirectory": tmpdir, }) processLocalizationFiles(settings) self.assertTrue(os.path.exists(os.path.join(tmpdir, "Testlang", "LC_MESSAGES", "calendarserver.mo"))) with translationTo('Testlang', localeDir=tmpdir): self.assertEquals(_("String1"), "string 1") self.assertEquals(_("String2"), "string 2")
def test_processArgs(self): """ Ensure utf-8 encoded command line args are handled properly """ content = """<plist version="1.0"> <dict> <key>key1</key> <string>before</string> </dict> </plist>""" self.fp.setContent(PREAMBLE + content) config = ConfigDict() writable = WritableConfig(config, self.configFile) writable.read() processArgs(writable, ["key1=\xf0\x9f\x92\xa3"], restart=False) writable2 = WritableConfig(config, self.configFile) writable2.read() self.assertEquals(writable2.currentConfigSubset, {'key1': u'\U0001f4a3'})
def setUp(self): super(InboundTests, self).setUp() yield self.buildStoreAndDirectory() self.receiver = MailReceiver(self.store, self.directory) self.retriever = MailRetriever( self.store, self.directory, ConfigDict({ "Type": "pop", "UseSSL": False, "Server": "example.com", "Port": 123, "Username": "******", })) def decorateTransaction(txn): txn._mailRetriever = self.retriever self.store.callWithNewTransactions(decorateTransaction) module = getModule(__name__) self.dataPath = module.filePath.sibling("data")
def test_setsParent(self): """ Test that certain values are set on the parent (i.e. twistd's Option's object) """ myConfig = ConfigDict(DEFAULT_CONFIG) myConfigFile = self.mktemp() writePlist(myConfig, myConfigFile) argv = [ "-f", myConfigFile, "-o", "PIDFile=/dev/null", "-o", "umask=63", # integers in plists & calendarserver command line are always # decimal; umask is traditionally in octal. ] self.config.parseOptions(argv) self.assertEquals(self.config.parent["pidfile"], "/dev/null") self.assertEquals(self.config.parent["umask"], 0077)
def test_updateMultiProcess(self): def stubProcessCount(*args): return 3 self.patch(twistedcaldav.stdconfig, "computeProcessCount", stubProcessCount) configDict = ConfigDict({ "MultiProcess" : { "ProcessCount" : 0, "MinProcessCount" : 2, "PerCPU" : 1, "PerGB" : 1, }, "Postgres" : { "ExtraConnections" : 5, "BuffersToConnectionsRatio" : 1.5, }, "SharedConnectionPool" : False, "MaxDBConnectionsPerPool" : 10, }) _updateMultiProcess(configDict) self.assertEquals(45, configDict.Postgres.MaxConnections) self.assertEquals(67, configDict.Postgres.SharedBuffers)
def test_keyPath(self): d = ConfigDict() setKeyPath(d, "one", "A") setKeyPath(d, "one", "B") setKeyPath(d, "two.one", "C") setKeyPath(d, "two.one", "D") setKeyPath(d, "two.two", "E") setKeyPath(d, "three.one.one", "F") setKeyPath(d, "three.one.two", "G") self.assertEquals(d.one, "B") self.assertEquals(d.two.one, "D") self.assertEquals(d.two.two, "E") self.assertEquals(d.three.one.one, "F") self.assertEquals(d.three.one.two, "G") self.assertEquals(getKeyPath(d, "one"), "B") self.assertEquals(getKeyPath(d, "two.one"), "D") self.assertEquals(getKeyPath(d, "two.two"), "E") self.assertEquals(getKeyPath(d, "three.one.one"), "F") self.assertEquals(getKeyPath(d, "three.one.two"), "G")
def buildTestDirectory(store, dataRoot, accounts=None, resources=None, augments=None, proxies=None, serversDB=None): """ @param store: the store for the directory to use @param dataRoot: the directory to copy xml files to @param accounts: path to the accounts.xml file @type accounts: L{FilePath} @param resources: path to the resources.xml file @type resources: L{FilePath} @param augments: path to the augments.xml file @type augments: L{FilePath} @param proxies: path to the proxies.xml file @type proxies: L{FilePath} @return: the directory service @rtype: L{IDirectoryService} """ defaultDirectory = FilePath(__file__).sibling("accounts") if accounts is None: accounts = defaultDirectory.child("accounts.xml") if resources is None: resources = defaultDirectory.child("resources.xml") if augments is None: augments = defaultDirectory.child("augments.xml") if proxies is None: proxies = defaultDirectory.child("proxies.xml") if not os.path.exists(dataRoot): os.makedirs(dataRoot) accountsCopy = FilePath(dataRoot).child("accounts.xml") accountsCopy.setContent(accounts.getContent()) resourcesCopy = FilePath(dataRoot).child("resources.xml") resourcesCopy.setContent(resources.getContent()) augmentsCopy = FilePath(dataRoot).child("augments.xml") augmentsCopy.setContent(augments.getContent()) proxiesCopy = FilePath(dataRoot).child("proxies.xml") proxiesCopy.setContent(proxies.getContent()) servicesInfo = ( ConfigDict({ "Enabled": True, "type": "xml", "params": { "xmlFile": "accounts.xml", "recordTypes": ("users", "groups"), }, }), ConfigDict({ "Enabled": True, "type": "xml", "params": { "xmlFile": "resources.xml", "recordTypes": ("locations", "resources", "addresses"), }, }), ) augmentServiceInfo = ConfigDict({ "type": "twistedcaldav.directory.augment.AugmentXMLDB", "params": { "xmlFiles": [ "augments.xml", ], "statSeconds": 15, }, }) wikiServiceInfo = ConfigDict({ "Enabled": True, "CollabHost": "localhost", "CollabPort": 4444, }) directory = buildDirectory(store, dataRoot, servicesInfo, augmentServiceInfo, wikiServiceInfo, serversDB) store.setDirectoryService(directory) return directory
def test_directoryFromConfig(self): config = ConfigDict({ "DataRoot": self.dataRoot, "Authentication": { "Wiki": { "Enabled": True, "CollabHost": "localhost", "CollabPort": 4444, }, }, "DirectoryService": { "Enabled": True, "type": "XML", "params": { "xmlFile": "accounts.xml", "recordTypes": ["users", "groups"], }, }, "ResourceService": { "Enabled": True, "type": "XML", "params": { "xmlFile": "resources.xml", "recordTypes": ["locations", "resources", "addresses"], }, }, "AugmentService": { "Enabled": True, # FIXME: This still uses an actual class name: "type": "twistedcaldav.directory.augment.AugmentXMLDB", "params": { "xmlFiles": ["augments.xml"], }, }, }) store = StubStore() service = directoryFromConfig(config, store=store) # Make sure XML files were created dataRoot = FilePath(self.dataRoot) self.assertTrue(dataRoot.child("accounts.xml").exists()) self.assertTrue(dataRoot.child("resources.xml").exists()) self.assertTrue(dataRoot.child("augments.xml").exists()) # Inspect the directory service structure self.assertTrue(isinstance(service, AugmentedDirectoryService)) self.assertTrue( isinstance(service._directory, AggregateDirectoryService)) self.assertEquals(len(service._directory.services), 4) self.assertTrue( isinstance(service._directory.services[0], XMLDirectoryService)) self.assertEquals(set(service._directory.services[0].recordTypes()), set([RecordType.user, RecordType.group])) self.assertTrue( isinstance(service._directory.services[1], XMLDirectoryService)) self.assertEquals( set(service._directory.services[1].recordTypes()), set([ CalRecordType.location, CalRecordType.resource, CalRecordType.address ])) self.assertTrue( isinstance(service._directory.services[2], DelegateDirectoryService)) self.assertEquals( set(service._directory.services[2].recordTypes()), set([ DelegateRecordType.readDelegateGroup, DelegateRecordType.writeDelegateGroup, DelegateRecordType.readDelegatorGroup, DelegateRecordType.writeDelegatorGroup, ])) self.assertTrue( isinstance(service._directory.services[3], WikiDirectoryService)) self.assertEquals(set(service._directory.services[3].recordTypes()), set([WikiRecordType.macOSXServerWiki])) # And make sure it's functional: record = yield service.recordWithUID("group07") self.assertEquals(record.fullNames, [u'Group 07'])
def test_ApplePushNotifierService(self): settings = ConfigDict({ "Enabled": True, "SubscriptionURL": "apn", "SubscriptionPurgeSeconds": 24 * 60 * 60, "SubscriptionPurgeIntervalSeconds": 24 * 60 * 60, "ProviderHost": "gateway.push.apple.com", "ProviderPort": 2195, "FeedbackHost": "feedback.push.apple.com", "FeedbackPort": 2196, "FeedbackUpdateSeconds": 300, "EnableStaggering": True, "StaggerSeconds": 3, "CalDAV": { "Enabled": True, "CertificatePath": "caldav.cer", "PrivateKeyPath": "caldav.pem", "AuthorityChainPath": "chain.pem", "Passphrase": "", "KeychainIdentity": "org.calendarserver.test", "Topic": "caldav_topic", }, "CardDAV": { "Enabled": True, "CertificatePath": "carddav.cer", "PrivateKeyPath": "carddav.pem", "AuthorityChainPath": "chain.pem", "Passphrase": "", "KeychainIdentity": "org.calendarserver.test", "Topic": "carddav_topic", }, }) # Add subscriptions txn = self._sqlCalendarStore.newTransaction() # Ensure empty values don't get through try: yield txn.addAPNSubscription("", "", "", "", "", "") except InvalidSubscriptionValues: pass try: yield txn.addAPNSubscription("", "1", "2", "3", "", "") except InvalidSubscriptionValues: pass token = "2d0d55cd7f98bcb81c6e24abcdc35168254c7846a43e2828b1ba5a8f82e219df" token2 = "3d0d55cd7f98bcb81c6e24abcdc35168254c7846a43e2828b1ba5a8f82e219df" key1 = "/CalDAV/calendars.example.com/user01/calendar/" timestamp1 = 1000 uid = "D2256BCC-48E2-42D1-BD89-CBA1E4CCDFFB" userAgent = "test agent" ipAddr = "127.0.0.1" yield txn.addAPNSubscription(token, key1, timestamp1, uid, userAgent, ipAddr) yield txn.addAPNSubscription(token2, key1, timestamp1, uid, userAgent, ipAddr) key2 = "/CalDAV/calendars.example.com/user02/calendar/" timestamp2 = 3000 yield txn.addAPNSubscription(token, key2, timestamp2, uid, userAgent, ipAddr) subscriptions = (yield txn.apnSubscriptionsBySubscriber(uid)) subscriptions = [[ record.token, record.resourceKey, record.modified, record.userAgent, record.ipAddr ] for record in subscriptions] self.assertTrue( [token, key1, timestamp1, userAgent, ipAddr] in subscriptions) self.assertTrue( [token, key2, timestamp2, userAgent, ipAddr] in subscriptions) self.assertTrue( [token2, key1, timestamp1, userAgent, ipAddr] in subscriptions) # Verify an update to a subscription with a different uid takes on # the new uid timestamp3 = 5000 uid2 = "D8FFB335-9D36-4CE8-A3B9-D1859E38C0DA" yield txn.addAPNSubscription(token, key2, timestamp3, uid2, userAgent, ipAddr) subscriptions = (yield txn.apnSubscriptionsBySubscriber(uid)) subscriptions = [[ record.token, record.resourceKey, record.modified, record.userAgent, record.ipAddr ] for record in subscriptions] self.assertTrue( [token, key1, timestamp1, userAgent, ipAddr] in subscriptions) self.assertFalse( [token, key2, timestamp3, userAgent, ipAddr] in subscriptions) subscriptions = (yield txn.apnSubscriptionsBySubscriber(uid2)) subscriptions = [[ record.token, record.resourceKey, record.modified, record.userAgent, record.ipAddr ] for record in subscriptions] self.assertTrue( [token, key2, timestamp3, userAgent, ipAddr] in subscriptions) # Change it back yield txn.addAPNSubscription(token, key2, timestamp2, uid, userAgent, ipAddr) yield txn.commit() # Set up the service (note since Clock has no 'callWhenRunning' we add our own) def callWhenRunning(callable, *args): callable(*args) clock = Clock() clock.callWhenRunning = callWhenRunning service = (yield ApplePushNotifierService.makeService( settings, self._sqlCalendarStore, testConnectorClass=TestConnector, reactor=clock)) self.assertEquals(set(service.providers.keys()), set(["CalDAV", "CardDAV"])) self.assertEquals(set(service.feedbacks.keys()), set(["CalDAV", "CardDAV"])) # First, enqueue a notification while we have no connection, in this # case by doing it prior to startService() # Notification arrives from calendar server dataChangedTimestamp = 1354815999 txn = self._sqlCalendarStore.newTransaction() yield service.enqueue(txn, "/CalDAV/calendars.example.com/user01/calendar/", dataChangedTimestamp=dataChangedTimestamp, priority=PushPriority.high) yield txn.commit() # The notifications should be in the queue self.assertTrue( ((token, key1), dataChangedTimestamp, PushPriority.high) in service.providers["CalDAV"].queue) self.assertTrue( ((token2, key1), dataChangedTimestamp, PushPriority.high) in service.providers["CalDAV"].queue) # Start the service, making the connection which should service the # queue service.startService() # The queue should be empty self.assertEquals(service.providers["CalDAV"].queue, []) # Verify data sent to APN providerConnector = service.providers["CalDAV"].testConnector rawData = providerConnector.transport.data self.assertEquals(len(rawData), 199) data = struct.unpack("!BI", rawData[:5]) self.assertEquals(data[0], 2) # command self.assertEquals(data[1], 194) # frame length # Item 1 (device token) data = struct.unpack("!BH32s", rawData[5:40]) self.assertEquals(data[0], 1) self.assertEquals(data[1], 32) self.assertEquals(data[2].encode("hex"), token.replace(" ", "")) # token # Item 2 (payload) data = struct.unpack("!BH", rawData[40:43]) self.assertEquals(data[0], 2) payloadLength = data[1] self.assertEquals(payloadLength, 138) payload = struct.unpack("!%ds" % (payloadLength, ), rawData[43:181]) payload = json.loads(payload[0]) self.assertEquals(payload["key"], u"/CalDAV/calendars.example.com/user01/calendar/") self.assertEquals(payload["dataChangedTimestamp"], dataChangedTimestamp) self.assertTrue("pushRequestSubmittedTimestamp" in payload) # Item 3 (notification id) data = struct.unpack("!BHI", rawData[181:188]) self.assertEquals(data[0], 3) self.assertEquals(data[1], 4) self.assertEquals(data[2], 2) # Item 4 (expiration) data = struct.unpack("!BHI", rawData[188:195]) self.assertEquals(data[0], 4) self.assertEquals(data[1], 4) # Item 5 (priority) data = struct.unpack("!BHB", rawData[195:199]) self.assertEquals(data[0], 5) self.assertEquals(data[1], 1) self.assertEquals(data[2], ApplePushPriority.high.value) # Verify token history is updated self.assertTrue(token in [ t for (_ignore_i, t) in providerConnector.service.protocol.history.history ]) self.assertTrue(token2 in [ t for (_ignore_i, t) in providerConnector.service.protocol.history.history ]) # # Verify staggering behavior # # Reset sent data providerConnector.transport.data = None # Send notification while service is connected txn = self._sqlCalendarStore.newTransaction() yield service.enqueue(txn, "/CalDAV/calendars.example.com/user01/calendar/", priority=PushPriority.low) yield txn.commit() clock.advance(1) # so that first push is sent self.assertEquals(len(providerConnector.transport.data), 199) # Ensure that the priority is "low" data = struct.unpack("!BHB", providerConnector.transport.data[195:199]) self.assertEquals(data[0], 5) self.assertEquals(data[1], 1) self.assertEquals(data[2], ApplePushPriority.low.value) # Reset sent data providerConnector.transport.data = None clock.advance(3) # so that second push is sent self.assertEquals(len(providerConnector.transport.data), 199) history = [] def errorTestFunction(status, identifier): history.append((status, identifier)) return succeed(None) # Simulate an error errorData = struct.pack("!BBI", APNProviderProtocol.COMMAND_ERROR, 1, 2) yield providerConnector.receiveData(errorData, fn=errorTestFunction) clock.advance(301) # Simulate multiple errors and dataReceived called # with amounts of data not fitting message boundaries # Send 1st 4 bytes history = [] errorData = struct.pack( "!BBIBBI", APNProviderProtocol.COMMAND_ERROR, 3, 4, APNProviderProtocol.COMMAND_ERROR, 5, 6, ) yield providerConnector.receiveData(errorData[:4], fn=errorTestFunction) # Send remaining bytes yield providerConnector.receiveData(errorData[4:], fn=errorTestFunction) self.assertEquals(history, [(3, 4), (5, 6)]) # Buffer is empty self.assertEquals(len(providerConnector.service.protocol.buffer), 0) # Sending 7 bytes yield providerConnector.receiveData("!" * 7, fn=errorTestFunction) # Buffer has 1 byte remaining self.assertEquals(len(providerConnector.service.protocol.buffer), 1) # Prior to feedback, there are 2 subscriptions txn = self._sqlCalendarStore.newTransaction() subscriptions = (yield txn.apnSubscriptionsByToken(token)) yield txn.commit() self.assertEquals(len(subscriptions), 2) # Simulate feedback with a single token feedbackConnector = service.feedbacks["CalDAV"].testConnector timestamp = 2000 binaryToken = token.decode("hex") feedbackData = struct.pack("!IH32s", timestamp, len(binaryToken), binaryToken) yield feedbackConnector.receiveData(feedbackData) # Simulate feedback with multiple tokens, and dataReceived called # with amounts of data not fitting message boundaries history = [] def feedbackTestFunction(timestamp, token): history.append((timestamp, token)) return succeed(None) timestamp = 2000 binaryToken = token.decode("hex") feedbackData = struct.pack( "!IH32sIH32s", timestamp, len(binaryToken), binaryToken, timestamp, len(binaryToken), binaryToken, ) # Send 1st 10 bytes yield feedbackConnector.receiveData(feedbackData[:10], fn=feedbackTestFunction) # Send remaining bytes yield feedbackConnector.receiveData(feedbackData[10:], fn=feedbackTestFunction) self.assertEquals(history, [(timestamp, token), (timestamp, token)]) # Buffer is empty self.assertEquals(len(feedbackConnector.service.protocol.buffer), 0) # Sending 39 bytes yield feedbackConnector.receiveData("!" * 39, fn=feedbackTestFunction) # Buffer has 1 byte remaining self.assertEquals(len(feedbackConnector.service.protocol.buffer), 1) # The second subscription should now be gone txn = self._sqlCalendarStore.newTransaction() subscriptions = (yield txn.apnSubscriptionsByToken(token)) yield txn.commit() self.assertEquals(len(subscriptions), 1) self.assertEqual(subscriptions[0].resourceKey, "/CalDAV/calendars.example.com/user02/calendar/") self.assertEqual(subscriptions[0].modified, 3000) self.assertEqual(subscriptions[0].subscriberGUID, "D2256BCC-48E2-42D1-BD89-CBA1E4CCDFFB") # Verify processError removes associated subscriptions and history # First find the id corresponding to token2 for (id, t) in providerConnector.service.protocol.history.history: if t == token2: break yield providerConnector.service.protocol.processError(8, id) # The token for this identifier is gone self.assertTrue( (id, token2) not in providerConnector.service.protocol.history.history) # All subscriptions for this token should now be gone txn = self._sqlCalendarStore.newTransaction() subscriptions = (yield txn.apnSubscriptionsByToken(token2)) yield txn.commit() self.assertEquals(subscriptions, []) # # Verify purgeOldAPNSubscriptions # # Create two subscriptions, one old and one new txn = self._sqlCalendarStore.newTransaction() now = int(time.time()) yield txn.addAPNSubscription(token2, key1, now - 2 * 24 * 60 * 60, uid, userAgent, ipAddr) # old yield txn.addAPNSubscription(token2, key2, now, uid, userAgent, ipAddr) # recent yield txn.commit() # Purge old subscriptions txn = self._sqlCalendarStore.newTransaction() yield txn.purgeOldAPNSubscriptions(now - 60 * 60) yield txn.commit() # Check that only the recent subscription remains txn = self._sqlCalendarStore.newTransaction() subscriptions = (yield txn.apnSubscriptionsByToken(token2)) yield txn.commit() self.assertEquals(len(subscriptions), 1) self.assertEquals(subscriptions[0].resourceKey, key2) service.stopService()
class BaseServiceMakerTests(TestCase): """ Utility class for ServiceMaker tests. """ configOptions = None def setUp(self): TestCase.setUp(self) self.options = TestCalDAVOptions() self.options.parent = Options() self.options.parent["gid"] = None self.options.parent["uid"] = None self.options.parent["nodaemon"] = None self.config = ConfigDict(DEFAULT_CONFIG) accountsFile = os.path.join(sourceRoot, "twistedcaldav/directory/test/accounts.xml") resourcesFile = os.path.join(sourceRoot, "twistedcaldav/directory/test/resources.xml") augmentsFile = os.path.join(sourceRoot, "twistedcaldav/directory/test/augments.xml") pemFile = os.path.join(sourceRoot, "twistedcaldav/test/data/server.pem") self.config["DirectoryService"] = { "params": {"xmlFile": accountsFile}, "type": "twistedcaldav.directory.xmlfile.XMLDirectoryService" } self.config["ResourceService"] = { "params": {"xmlFile": resourcesFile}, } self.config["AugmentService"] = { "params": {"xmlFiles": [augmentsFile]}, "type": "twistedcaldav.directory.augment.AugmentXMLDB" } self.config.ServerRoot = self.mktemp() self.config.ConfigRoot = "config" self.config.ProcessType = "Slave" self.config.SSLPrivateKey = pemFile self.config.SSLCertificate = pemFile self.config.Memcached.Pools.Default.ClientEnabled = False self.config.Memcached.Pools.Default.ServerEnabled = False self.config.DirectoryAddressBook.Enabled = False self.config.SudoersFile = "" if self.configOptions: self.config.update(self.configOptions) os.mkdir(self.config.ServerRoot) os.mkdir(os.path.join(self.config.ServerRoot, self.config.DocumentRoot)) os.mkdir(os.path.join(self.config.ServerRoot, self.config.DataRoot)) os.mkdir(os.path.join(self.config.ServerRoot, self.config.ConfigRoot)) self.configFile = self.mktemp() self.writeConfig() def tearDown(self): config.setDefaults(DEFAULT_CONFIG) config.reset() def writeConfig(self): """ Flush self.config out to self.configFile """ writePlist(self.config, self.configFile) def makeService(self): """ Create a service by calling into CalDAVServiceMaker with self.configFile """ self.options.parseOptions(["-f", self.configFile]) return CalDAVServiceMaker().makeService(self.options) def getSite(self): """ Get the server.Site from the service by finding the HTTPFactory """ service = self.makeService() return service.services[0].args[1].protocolArgs["requestFactory"]
def test_directoryFromConfig(self): config = ConfigDict({ "DataRoot": self.dataRoot, "Authentication": { "Wiki": { "Enabled": True, "EndpointDescriptor": "tcp:host=localhost:port=4444", }, }, "DirectoryService": { "Enabled": True, "type": "xml", "params": { "xmlFile": "accounts.xml", "recordTypes": ["users", "groups"], }, }, "ResourceService": { "Enabled": True, "type": "xml", "params": { "xmlFile": "resources.xml", "recordTypes": ["locations", "resources", "addresses"], }, }, "AugmentService": { "type": "xml", "params": { "xmlFiles": ["augments.xml"], }, }, "Servers": { "Enabled": False, }, "DirectoryProxy": { "Enabled": False, "SocketPath": "directory-proxy.sock", "InSidecarCachingSeconds": 120, }, "DirectoryCaching": { "CachingSeconds": 60, "NegativeCachingEnabled": True, "LookupsBetweenPurges": 0, }, "DirectoryFilterStartsWith": False, }) store = StubStore() service = directoryFromConfig(config, store) # Make sure XML files were created dataRoot = FilePath(self.dataRoot) self.assertTrue(dataRoot.child("accounts.xml").exists()) self.assertTrue(dataRoot.child("resources.xml").exists()) self.assertTrue(dataRoot.child("augments.xml").exists()) # Inspect the directory service structure self.assertTrue(isinstance(service, AugmentedDirectoryService)) self.assertTrue( isinstance(service._directory, AggregateDirectoryService)) self.assertEquals(len(service._directory.services), 4) self.assertTrue( isinstance(service._directory.services[0], DelegateDirectoryService)) self.assertEquals( set(service._directory.services[0].recordTypes()), set([ DelegateRecordType.readDelegateGroup, DelegateRecordType.writeDelegateGroup, DelegateRecordType.readDelegatorGroup, DelegateRecordType.writeDelegatorGroup, ])) self.assertTrue( isinstance(service._directory.services[1], CachingDirectoryService)) self.assertTrue( isinstance(service._directory.services[1]._directory, XMLDirectoryService)) self.assertEquals( set(service._directory.services[1]._directory.recordTypes()), set([RecordType.user, RecordType.group])) self.assertTrue( isinstance(service._directory.services[2]._directory, XMLDirectoryService)) self.assertEquals( set(service._directory.services[2]._directory.recordTypes()), set([ CalRecordType.location, CalRecordType.resource, CalRecordType.address ])) self.assertTrue( isinstance(service._directory.services[3], WikiDirectoryService)) self.assertEquals(set(service._directory.services[3].recordTypes()), set([WikiRecordType.macOSXServerWiki])) # And make sure it's functional: record = yield service.recordWithUID("group07") self.assertEquals(record.fullNames, [u'Group 07'])
class BaseServiceMakerTests(StoreTestCase): """ Utility class for ServiceMaker tests. """ configOptions = None @inlineCallbacks def setUp(self): yield super(BaseServiceMakerTests, self).setUp() self.options = TestCalDAVOptions() self.options.parent = Options() self.options.parent["gid"] = None self.options.parent["uid"] = None self.options.parent["nodaemon"] = None self.config = ConfigDict(DEFAULT_CONFIG) accountsFile = os.path.join(sourceRoot, "twistedcaldav/directory/test/accounts.xml") resourcesFile = os.path.join(sourceRoot, "twistedcaldav/directory/test/resources.xml") augmentsFile = os.path.join(sourceRoot, "twistedcaldav/directory/test/augments.xml") pemFile = os.path.join(sourceRoot, "twistedcaldav/test/data/server.pem") self.config["DirectoryService"] = { "params": {"xmlFile": accountsFile}, "type": "twistedcaldav.directory.xmlfile.XMLDirectoryService" } self.config["ResourceService"] = { "params": {"xmlFile": resourcesFile}, } self.config["AugmentService"] = { "params": {"xmlFiles": [augmentsFile]}, "type": "twistedcaldav.directory.augment.AugmentXMLDB" } self.config.UseDatabase = False self.config.ServerRoot = self.mktemp() self.config.ConfigRoot = "config" self.config.ProcessType = "Single" self.config.SSLPrivateKey = pemFile self.config.SSLCertificate = pemFile self.config.EnableSSL = True self.config.Memcached.Pools.Default.ClientEnabled = False self.config.Memcached.Pools.Default.ServerEnabled = False self.config.DirectoryAddressBook.Enabled = False self.config.UsePackageTimezones = True if self.configOptions: self.config.update(self.configOptions) os.mkdir(self.config.ServerRoot) os.mkdir(os.path.join(self.config.ServerRoot, self.config.DocumentRoot)) os.mkdir(os.path.join(self.config.ServerRoot, self.config.DataRoot)) os.mkdir(os.path.join(self.config.ServerRoot, self.config.ConfigRoot)) self.configFile = self.mktemp() self.writeConfig() def tearDown(self): config.setDefaults(DEFAULT_CONFIG) config.reset() def writeConfig(self): """ Flush self.config out to self.configFile """ writePlist(self.config, self.configFile) def makeService(self, patcher=passthru): """ Create a service by calling into CalDAVServiceMaker with self.configFile """ self.options.parseOptions(["-f", self.configFile]) maker = CalDAVServiceMaker() maker = patcher(maker) return maker.makeService(self.options) def getSite(self): """ Get the server.Site from the service by finding the HTTPFactory. """ service = self.makeService() for listeningService in inServiceHierarchy( service, # FIXME: need a better predicate for 'is this really an HTTP # factory' but this works for now. # NOTE: in a database 'single' configuration, PostgresService # will prevent the HTTP services from actually getting added to # the hierarchy until the hierarchy has started. # 'underlyingSite' assigned in caldav.py lambda x: hasattr(x, 'underlyingSite') ): return listeningService.underlyingSite raise RuntimeError("No site found.")
def test_readInvalidXML(self): self.fp.setContent("invalid") config = ConfigDict() writable = WritableConfig(config, self.configFile) self.assertRaises(ExpatError, writable.read)