OwlCyberSecurity - MANAGER
Edit File: notifications.py
## # Copyright (c) 2010-2017 Apple 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. ## """ Implements notification functionality. """ __all__ = [ "NotificationResource", "NotificationCollectionResource", ] from twext.python.log import Logger from txweb2 import responsecode from txdav.xml import element as davxml from twisted.internet.defer import succeed, inlineCallbacks, returnValue,\ maybeDeferred from twistedcaldav.resource import ReadOnlyNoCopyResourceMixIn, CalDAVResource from twistedcaldav.sql import AbstractSQLDatabase, db_prefix from txdav.common.icommondatastore import SyncTokenValidException import json import os log = Logger() class NotificationResource(CalDAVResource): """ An xml resource in a Notification collection. """ def __init__(self, parent): self._parent = parent CalDAVResource.__init__(self) def principalCollections(self): return self._parent.principalCollections() def isCollection(self): return False def resourceName(self): raise NotImplementedError def http_PUT(self, request): return responsecode.FORBIDDEN @inlineCallbacks def http_DELETE(self, request): response = (yield super(NotificationResource, self).http_DELETE(request)) if response == responsecode.NO_CONTENT: yield self._parent.removedNotifictionMessage(request, self.resourceName()) returnValue(response) class NotificationCollectionResource(ReadOnlyNoCopyResourceMixIn, CalDAVResource): def notificationsDB(self): if not hasattr(self, "_notificationsDB"): self._notificationsDB = NotificationsDatabase(self) return self._notificationsDB def isCollection(self): return True def resourceType(self): return davxml.ResourceType.notification def getNotifictionMessages(self, request, componentType=None, returnLatestVersion=True): return succeed([]) def getNotifictionMessageByUID(self, request, uid): return maybeDeferred(self.notificationsDB().recordForUID, uid) @inlineCallbacks def deleteNotifictionMessageByUID(self, request, uid): # See if it exists and delete the resource record = yield self.notificationsDB().recordForUID(uid) if record: yield self.deleteNotification(request, record) @inlineCallbacks def deleteNotifictionMessageByName(self, request, rname): # See if it exists and delete the resource record = yield self.notificationsDB().recordForName(rname) if record: yield self.deleteNotification(request, record) returnValue(None) @inlineCallbacks def deleteNotification(self, request, record): yield self._deleteNotification(request, record.name) yield self.notificationsDB().removeRecordForUID(record.uid) def removedNotifictionMessage(self, request, rname): return maybeDeferred(self.notificationsDB().removeRecordForName, rname) class NotificationRecord(object): def __init__(self, uid, name, notificationtype): self.uid = uid self.name = name self.notificationtype = notificationtype if isinstance(notificationtype, dict) else json.loads(notificationtype) class NotificationsDatabase(AbstractSQLDatabase): log = Logger() db_basename = db_prefix + "notifications" schema_version = "1" db_type = "notifications" def __init__(self, resource): """ @param resource: the L{CalDAVResource} resource for the notifications collection.) """ self.resource = resource db_filename = os.path.join(self.resource.fp.path, NotificationsDatabase.db_basename) super(NotificationsDatabase, self).__init__(db_filename, True, autocommit=True) self.resource._txn.postCommit(self._db_close) self.resource._txn.postAbort(self._db_close) def allRecords(self): records = self._db_execute("select * from NOTIFICATIONS") return [self._makeRecord(row) for row in (records if records is not None else ())] def recordForUID(self, uid): row = self._db_execute("select * from NOTIFICATIONS where UID = :1", uid) return self._makeRecord(row[0]) if row else None def addOrUpdateRecord(self, record): self._db_execute( """insert or replace into NOTIFICATIONS (UID, NAME, TYPE) values (:1, :2, :3) """, record.uid, record.name, json.dumps(record.notificationtype), ) self._db_execute( """ insert or replace into REVISIONS (NAME, REVISION, DELETED) values (:1, :2, :3) """, record.name, self.bumpRevision(fast=True), 'N', ) def removeRecordForUID(self, uid): record = self.recordForUID(uid) self.removeRecordForName(record.name) def removeRecordForName(self, rname): self._db_execute("delete from NOTIFICATIONS where NAME = :1", rname) self._db_execute( """ update REVISIONS SET REVISION = :1, DELETED = :2 where NAME = :3 """, self.bumpRevision(fast=True), 'Y', rname ) def whatchanged(self, revision): results = [(name.encode("utf-8"), deleted) for name, deleted in self._db_execute("select NAME, DELETED from REVISIONS where REVISION > :1", revision)] results.sort(key=lambda x: x[1]) changed = [] deleted = [] for name, wasdeleted in results: if name: if wasdeleted == 'Y': if revision: deleted.append(name) else: changed.append(name) else: raise SyncTokenValidException return changed, deleted, def lastRevision(self): return self._db_value_for_sql( "select REVISION from REVISION_SEQUENCE" ) def bumpRevision(self, fast=False): self._db_execute( """ update REVISION_SEQUENCE set REVISION = REVISION + 1 """, ) self._db_commit() return self._db_value_for_sql( """ select REVISION from REVISION_SEQUENCE """, ) def _db_version(self): """ @return: the schema version assigned to this index. """ return NotificationsDatabase.schema_version def _db_type(self): """ @return: the collection type assigned to this index. """ return NotificationsDatabase.db_type def _db_init_data_tables(self, q): """ Initialise the underlying database tables. @param q: a database cursor to use. """ # # NOTIFICATIONS table is the primary table # UID: UID for this notification # NAME: child resource name # TYPE: type of notification # q.execute( """ create table NOTIFICATIONS ( UID text unique, NAME text unique, TYPE text ) """ ) q.execute( """ create index UID on NOTIFICATIONS (UID) """ ) # # REVISIONS table tracks changes # NAME: Last URI component (eg. <uid>.ics, RESOURCE primary key) # REVISION: revision number # WASDELETED: Y if revision deleted, N if added or changed # q.execute( """ create table REVISION_SEQUENCE ( REVISION integer ) """ ) q.execute( """ insert into REVISION_SEQUENCE (REVISION) values (0) """ ) q.execute( """ create table REVISIONS ( NAME text unique, REVISION integer, DELETED text(1) ) """ ) q.execute( """ create index REVISION on REVISIONS (REVISION) """ ) def _db_upgrade_data_tables(self, q, old_version): """ Upgrade the data from an older version of the DB. """ # Nothing to do as we have not changed the schema pass def _makeRecord(self, row): return NotificationRecord( *[str(item) if isinstance(item, unicode) else item for item in row] )