[CalendarServer-changes] [9802] CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/ scheduling/ischedule
source_changes at macosforge.org
source_changes at macosforge.org
Tue Sep 11 08:26:58 PDT 2012
Revision: 9802
http://trac.macosforge.org/projects/calendarserver/changeset/9802
Author: cdaboo at apple.com
Date: 2012-09-11 08:26:56 -0700 (Tue, 11 Sep 2012)
Log Message:
-----------
Keep glyph happy - code formatting changes only (though I still won't truncate long lines).
Modified Paths:
--------------
CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/delivery.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/dkim.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/resource.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/servers.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/test/test_dkim.py
CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/xml.py
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/delivery.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/delivery.py 2012-09-10 23:38:10 UTC (rev 9801)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/delivery.py 2012-09-11 15:26:56 UTC (rev 9802)
@@ -42,7 +42,7 @@
from twistedcaldav.scheduling.ischedule.servers import IScheduleServerRecord
from twistedcaldav.scheduling.itip import iTIPRequestStatus
from twistedcaldav.util import utf8String, normalizationLookup
-from twistedcaldav.scheduling.cuaddress import PartitionedCalendarUser, RemoteCalendarUser,\
+from twistedcaldav.scheduling.cuaddress import PartitionedCalendarUser, RemoteCalendarUser, \
OtherServerCalendarUser
from twext.internet.gaiendpoint import GAIEndpoint
@@ -57,25 +57,29 @@
log = Logger()
+
+
class ScheduleViaISchedule(DeliveryService):
-
+
@classmethod
def serviceType(cls):
return DeliveryService.serviceType_ischedule
+
@classmethod
def matchCalendarUserAddress(cls, cuaddr):
# TODO: here is where we would attempt service discovery based on the cuaddr.
-
+
# Do default match
return super(ScheduleViaISchedule, cls).matchCalendarUserAddress(cuaddr)
+
def generateSchedulingResponses(self, refreshOnly=False):
"""
Generate scheduling responses for remote recipients.
"""
-
+
# Group recipients by server so that we can do a single request with multiple recipients
# to each different server.
groups = {}
@@ -98,10 +102,10 @@
"No server for recipient",
))
self.responses.add(recipient.cuaddr, Failure(exc_value=err), reqstatus=iTIPRequestStatus.NO_USER_SUPPORT)
-
+
# Process next recipient
continue
-
+
if not server.allow_to:
# Cannot do server-to-server outgoing requests for this server.
err = HTTPError(ErrorResponse(
@@ -110,10 +114,10 @@
"Cannot send to recipient's server",
))
self.responses.add(recipient.cuaddr, Failure(exc_value=err), reqstatus=iTIPRequestStatus.SERVICE_UNAVAILABLE)
-
+
# Process next recipient
continue
-
+
groups.setdefault(server, []).append(recipient)
if len(groups) == 0:
@@ -129,34 +133,38 @@
return DeferredList(deferreds)
+
def _getServerForPartitionedUser(self, recipient):
-
+
if not hasattr(self, "partitionedServers"):
self.partitionedServers = {}
-
+
partition = recipient.principal.partitionURI()
if partition not in self.partitionedServers:
self.partitionedServers[partition] = IScheduleServerRecord(uri=joinURL(partition, "/ischedule"))
self.partitionedServers[partition].unNormalizeAddresses = False
self.partitionedServers[partition].moreHeaders.append(recipient.principal.server().secretHeader())
-
+
return self.partitionedServers[partition]
+
def _getServerForOtherServerUser(self, recipient):
-
+
if not hasattr(self, "otherServers"):
self.otherServers = {}
-
+
serverURI = recipient.principal.serverURI()
if serverURI not in self.otherServers:
self.otherServers[serverURI] = IScheduleServerRecord(uri=joinURL(serverURI, "/ischedule"))
self.otherServers[serverURI].unNormalizeAddresses = not recipient.principal.server().isImplicit
self.otherServers[serverURI].moreHeaders.append(recipient.principal.server().secretHeader())
-
+
return self.otherServers[serverURI]
+
+
class IScheduleRequest(object):
-
+
def __init__(self, scheduler, server, recipients, responses, refreshOnly=False):
self.scheduler = scheduler
@@ -164,13 +172,14 @@
self.recipients = recipients
self.responses = responses
self.refreshOnly = refreshOnly
-
+
self._generateHeaders()
self._prepareData()
-
+
+
@inlineCallbacks
def doRequest(self):
-
+
# Generate an HTTP client request
try:
if not hasattr(self.scheduler.request, "extendedLogItems"):
@@ -188,14 +197,14 @@
else:
ep = GAIEndpoint(reactor, self.server.host, self.server.port)
proto = (yield ep.connect(f))
-
+
request = ClientRequest("POST", self.server.path, self.headers, self.data)
yield self.logRequest("debug", "Sending server-to-server POST request:", request)
response = (yield proto.submitRequest(request))
-
+
yield self.logResponse("debug", "Received server-to-server POST response:", response)
xml = (yield davXMLFromStream(response.stream))
-
+
self._parseResponse(xml)
except Exception, e:
@@ -209,6 +218,7 @@
))
self.responses.add(recipient.cuaddr, Failure(exc_value=err), reqstatus=iTIPRequestStatus.SERVICE_UNAVAILABLE)
+
def logRequest(self, level, message, request, **kwargs):
"""
Log an HTTP request.
@@ -232,25 +242,26 @@
else:
iostr.write("%s: xxxxxxxxx\n" % (name,))
iostr.write("\n")
-
+
# We need to play a trick with the request stream as we can only read it once. So we
# read it, store the value in a MemoryStream, and replace the request's stream with that,
# so the data can be read again.
def _gotData(data):
iostr.write(data)
-
+
request.stream = MemoryStream(data if data is not None else "")
request.stream.doStartReading = None
-
+
log.emit(level, iostr.getvalue(), **kwargs)
d = allDataFromStream(request.stream)
d.addCallback(_gotData)
return d
-
+
else:
return succeed(None)
-
+
+
def logResponse(self, level, message, response, **kwargs):
"""
Log an HTTP request.
@@ -270,40 +281,42 @@
else:
iostr.write("%s: xxxxxxxxx\n" % (name,))
iostr.write("\n")
-
+
# We need to play a trick with the response stream to ensure we don't mess it up. So we
# read it, store the value in a MemoryStream, and replace the response's stream with that,
# so the data can be read again.
def _gotData(data):
iostr.write(data)
-
+
response.stream = MemoryStream(data if data is not None else "")
response.stream.doStartReading = None
-
+
log.emit(level, iostr.getvalue(), **kwargs)
-
+
d = allDataFromStream(response.stream)
d.addCallback(_gotData)
return d
+
def _generateHeaders(self):
self.headers = Headers()
self.headers.setHeader('Host', utf8String(self.server.host + ":%s" % (self.server.port,)))
-
+
# The Originator must be the ORGANIZER (for a request) or ATTENDEE (for a reply)
self.headers.addRawHeader('Originator', utf8String(self.scheduler.organizer.cuaddr if self.scheduler.isiTIPRequest else self.scheduler.attendee))
self._doAuthentication()
for recipient in self.recipients:
self.headers.addRawHeader('Recipient', utf8String(recipient.cuaddr))
- self.headers.setHeader('Content-Type', MimeType("text", "calendar", params={"charset":"utf-8"}))
+ self.headers.setHeader('Content-Type', MimeType("text", "calendar", params={"charset": "utf-8"}))
# Add any additional headers
for name, value in self.server.moreHeaders:
self.headers.addRawHeader(name, value)
-
+
if self.refreshOnly:
self.headers.addRawHeader("X-CALENDARSERVER-ITIP-REFRESHONLY", "T")
+
def _doAuthentication(self):
if self.server.authentication and self.server.authentication[0] == "basic":
self.headers.setHeader(
@@ -311,8 +324,9 @@
('Basic', ("%s:%s" % (self.server.authentication[1], self.server.authentication[2],)).encode('base64')[:-1])
)
+
def _prepareData(self):
- if self.server.unNormalizeAddresses and self.scheduler.method == "PUT":
+ if self.server.unNormalizeAddresses and self.scheduler.method == "PUT":
normalizedCalendar = self.scheduler.calendar.duplicate()
normalizedCalendar.normalizeCalendarUserAddresses(
normalizationLookup,
@@ -322,13 +336,14 @@
normalizedCalendar = self.scheduler.calendar
self.data = str(normalizedCalendar)
+
def _parseResponse(self, xml):
# Check for correct root element
schedule_response = xml.root_element
if not isinstance(schedule_response, caldavxml.ScheduleResponse) or not schedule_response.children:
raise HTTPError(responsecode.BAD_REQUEST)
-
+
# Parse each response - do this twice: once looking for errors that will
# result in all recipients shown as failures; the second loop adds all the
# valid responses to the actual result.
@@ -341,4 +356,3 @@
raise HTTPError(responsecode.BAD_REQUEST)
for response in schedule_response.children:
self.responses.clone(response)
-
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/dkim.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/dkim.py 2012-09-10 23:38:10 UTC (rev 9801)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/dkim.py 2012-09-11 15:26:56 UTC (rev 9802)
@@ -43,9 +43,9 @@
log = Logger()
# DKIM/iSchedule Constants
-RSA1 = "rsa-sha1"
+RSA1 = "rsa-sha1"
RSA256 = "rsa-sha256"
-Q_DNS = "dns/txt"
+Q_DNS = "dns/txt"
Q_HTTP = "http/well-known"
Q_PRIVATE = "private-exchange"
@@ -57,11 +57,13 @@
ISCHEDULE_VERSION_VALUE = "1.0"
ISCHEDULE_MESSAGE_ID = "iSchedule-Message-ID"
+
+
class DKIMUtils(object):
"""
Some useful functions.
"""
-
+
@staticmethod
def validConfiguration(config):
if config.Scheduling.iSchedule.DKIM.Enabled:
@@ -75,12 +77,12 @@
msg = "DKIM: No selector specified"
log.error(msg)
raise ConfigurationError(msg)
-
+
if config.Scheduling.iSchedule.DKIM.SignatureAlgorithm not in (RSA1, RSA256):
msg = "DKIM: Invalid algorithm: %s" % (config.Scheduling.iSchedule.SignatureAlgorithm,)
log.error(msg)
raise ConfigurationError(msg)
-
+
try:
with open(config.Scheduling.iSchedule.DKIM.PrivateKeyFile) as f:
key_data = f.read()
@@ -94,7 +96,7 @@
msg = "DKIM: Invalid private key file: %s" % (config.Scheduling.iSchedule.DKIM.PrivateKeyFile,)
log.error(msg)
raise ConfigurationError(msg)
-
+
try:
with open(config.Scheduling.iSchedule.DKIM.PublicKeyFile) as f:
key_data = f.read()
@@ -108,7 +110,7 @@
msg = "DKIM: Invalid public key file: %s" % (config.Scheduling.iSchedule.DKIM.PublicKeyFile,)
log.error(msg)
raise ConfigurationError(msg)
-
+
if config.Scheduling.iSchedule.DKIM.PrivateExchanges:
if not os.path.exists(config.Scheduling.iSchedule.DKIM.PrivateExchanges):
try:
@@ -125,7 +127,8 @@
log.info("DKIM: Enabled")
else:
log.info("DKIM: Disabled")
-
+
+
@staticmethod
def hashlib_method(algorithm):
"""
@@ -135,7 +138,8 @@
RSA1 : hashlib.sha1,
RSA256: hashlib.sha256,
}[algorithm]
-
+
+
@staticmethod
def hash_name(algorithm):
"""
@@ -163,16 +167,17 @@
pass
return dkim_tags
+
@staticmethod
def canonicalizeHeader(name, value, remove_b=None):
"""
Canonicalize the header using "relaxed" method. Optionally remove the b= value from
any DKIM-Signature present.
-
+
FIXME: this needs to be smarter about where valid WSP can occur in a header. Right now it will
blindly collapse all runs of SP/HTAB into a single SP. That could be wrong if a legitimate sequence of
SP/HTAB occurs in a header value.
-
+
@param name: header name
@type name: C{str}
@param value: header value
@@ -180,7 +185,7 @@
@param remove_b: the b= value to remove, or C{None} if no removal needed
@type remove_b: C{str} or C{None}
"""
-
+
# Basic relaxed behavior
name = name.lower()
value = " ".join(value.split())
@@ -193,13 +198,13 @@
return "%s:%s\r\n" % (name, value,)
-
+
class DKIMRequest(ClientRequest):
"""
A ClientRequest that optionally creates a DKIM signature.
"""
-
+
keys = {}
def __init__(
@@ -220,12 +225,12 @@
):
"""
Create a DKIM request, which is a regular client request with the additional information needed to sign the message.
-
+
@param method: HTTP method to use
@type method: C{str}
@param uri: request-URI
@type uri: C{str}
- @param headers: request headers
+ @param headers: request headers
@type headers: L{http_headers}
@param stream: body data
@type stream: L{Stream}
@@ -245,7 +250,7 @@
@type useHTTPKey: C{bool}
@param usePrivateExchangeKey: whether or not to add private-exchange as a key lookup option
@type usePrivateExchangeKey: C{bool}
- @param expire: number of seconds to expiration of signature
+ @param expire: number of seconds to expiration of signature
@type expire: C{int}
"""
super(DKIMRequest, self).__init__(method, uri, headers, stream)
@@ -256,7 +261,7 @@
self.sign_headers = sign_headers
self.time = str(int(time.time()))
self.expire = str(int(time.time() + expire))
-
+
assert self.domain
assert self.selector
assert self.algorithm in (RSA1, RSA256,)
@@ -264,12 +269,15 @@
self.hash_method = DKIMUtils.hashlib_method(self.algorithm)
self.hash_name = DKIMUtils.hash_name(self.algorithm)
-
+
self.keyMethods = []
- if useDNSKey: self.keyMethods.append(Q_DNS)
- if useHTTPKey: self.keyMethods.append(Q_HTTP)
- if usePrivateExchangeKey: self.keyMethods.append(Q_PRIVATE)
-
+ if useDNSKey:
+ self.keyMethods.append(Q_DNS)
+ if useHTTPKey:
+ self.keyMethods.append(Q_HTTP)
+ if usePrivateExchangeKey:
+ self.keyMethods.append(Q_PRIVATE)
+
self.message_id = str(uuid.uuid4())
@@ -280,23 +288,23 @@
be no changes to the request (no headers, no body change) after it is called.
"""
- # Get the headers and the DKIM-Signature tags
+ # Get the headers and the DKIM-Signature tags
headers, dkim_tags = (yield self.signatureHeaders())
# Sign the hash
signature = self.generateSignature(headers)
-
+
# Complete the header
dkim_tags[-1] = ("b", signature,)
dkim_header = "; ".join(["%s=%s" % item for item in dkim_tags])
self.headers.addRawHeader(DKIM_SIGNATURE, dkim_header)
-
+
log.debug("DKIM: Generated header: DKIM-Signature:%s" % (dkim_header,))
log.debug("DKIM: Signed headers:\n%s" % (headers,))
returnValue(signature)
-
+
@inlineCallbacks
def bodyHash(self):
"""
@@ -318,7 +326,7 @@
"""
Generate the headers that are going to be signed as well as the DKIM-Signature tags.
"""
-
+
# Make sure we have the required iSchedule headers
self.headers.addRawHeader(ISCHEDULE_VERSION, ISCHEDULE_VERSION_VALUE)
self.headers.addRawHeader(ISCHEDULE_MESSAGE_ID, self.message_id)
@@ -378,11 +386,12 @@
pass
+
class DKIMVerifier(object):
"""
Class used to verify an DKIM-signed HTTP request.
"""
-
+
def __init__(self, request, key_lookup=None):
"""
@param request: The HTTP request to process
@@ -396,27 +405,26 @@
PublicKeyLookup_HTTP_WellKnown,
PublicKeyLookup_DNSTXT,
) if key_lookup is None else key_lookup
-
-
+
@inlineCallbacks
def verify(self):
"""
@raise: DKIMVerificationError
"""
-
+
# Check presence of DKIM header
self.processDKIMHeader()
-
+
# Extract the set of canonicalized headers being signed
headers = self.extractSignedHeaders()
log.debug("DKIM: Signed headers:\n%s" % (headers,))
-
+
# Locate the public key
pubkey = (yield self.locatePublicKey())
if pubkey is None:
- raise DKIMVerificationError("No public key to verify the DKIM signature")
-
+ raise DKIMVerificationError("No public key to verify the DKIM signature")
+
# Do header verification
try:
rsa.verify(headers, base64.b64decode(self.dkim_tags["b"]), pubkey)
@@ -435,14 +443,14 @@
log.debug("DKIM: %s: DKIM-Signature:%s" % (msg, self.request.headers.getRawHeaders(DKIM_SIGNATURE),))
raise DKIMVerificationError(msg)
-
+
def processDKIMHeader(self):
"""
Extract the DKIM-Signature header and process the tags.
-
+
@raise: DKIMVerificationError
"""
-
+
# Check presence of header
dkim = self.request.headers.getRawHeaders(DKIM_SIGNATURE)
if dkim is None:
@@ -460,7 +468,7 @@
# Extract tags from the header
self.dkim_tags = DKIMUtils.extractTags(dkim)
-
+
# Verify validity of tags
required_tags = ("v", "a", "b", "bh", "c", "d", "h", "s", "http",)
for tag in required_tags:
@@ -478,7 +486,7 @@
for tag, values in check_values.items():
if tag not in required_tags and tag not in self.dkim_tags:
pass
-
+
# Handle some structured values
if tag == "q":
test = self.dkim_tags[tag].split(":")
@@ -519,7 +527,6 @@
msg = "Tag: http request-URI does not match: %s" % (uri,)
log.debug("DKIM: " + msg)
raise DKIMVerificationError(msg)
-
# Some useful bits
self.hash_method = DKIMUtils.hashlib_method(self.dkim_tags["a"])
@@ -536,7 +543,7 @@
# headers - a technique used to ensure headers cannot be added in transit
header_list = [hdr.strip() for hdr in self.dkim_tags["h"].split(":")]
header_counter = collections.defaultdict(int)
-
+
headers = []
for header in header_list:
actual_headers = self.request.headers.getRawHeaders(header)
@@ -559,7 +566,7 @@
"""
Try to lookup the public key matching the signature.
"""
-
+
for lookup in self.key_lookup_methods:
if lookup.method in self.key_methods or lookup.method == "*":
pubkey = (yield lookup(self.dkim_tags).getPublicKey())
@@ -567,24 +574,24 @@
returnValue(pubkey)
else:
returnValue(None)
-
+
class PublicKeyLookup(object):
"""
Abstract base class for public key lookup methods.
-
+
The L{method} attribute indicated the DKIM q= lookup method that the class will support, or if set to "*",
the class will handle any q= value.
"""
-
+
keyCache = {}
method = None
def __init__(self, dkim_tags):
self.dkim_tags = dkim_tags
-
-
+
+
@inlineCallbacks
def getPublicKey(self, useCache=True):
"""
@@ -597,37 +604,37 @@
if key not in PublicKeyLookup.keyCache or not useCache:
pubkeys = (yield self._lookupKeys())
PublicKeyLookup.keyCache[key] = pubkeys
-
+
returnValue(self._selectKey())
-
-
+
+
def _getSelectorKey(self):
"""
Get a token used to uniquely identify the key being looked up. Token format will
depend on the lookup method.
"""
raise NotImplementedError
-
-
+
+
def _lookupKeys(self):
"""
Do the key lookup using the actual lookup method. Return a C{list} of C{dict}
that contains the key tag-list. Return a L{Deferred}.
"""
raise NotImplementedError
-
-
+
+
def _selectKey(self):
"""
Select a specific key from the list that best matches the DKIM-Signature tags
"""
-
+
pubkeys = PublicKeyLookup.keyCache.get(self._getSelectorKey(), [])
for pkey in pubkeys:
# Check validity
if pkey.get("v", "DKIM1") != "DKIM1":
continue
-
+
# Check key type
if pkey.get("k", "rsa") != "rsa":
continue
@@ -636,21 +643,21 @@
hashes = set([hash.strip() for hash in pkey.get("h", "sha1:sha256").split(":")])
if self.dkim_tags["a"][4:] not in hashes:
continue
-
+
# Service type
if pkey.get("s", KEY_SERVICE_TYPE) not in ("*", KEY_SERVICE_TYPE,):
continue
-
+
# Non-revoked key
if len(pkey.get("p", "")) == 0:
continue
-
+
return self._makeKey(pkey)
-
+
log.debug("DKIM: No valid public key: %s %s" % (self._getSelectorKey(), pubkeys,))
return None
-
-
+
+
def _makeKey(self, pkey):
"""
Turn the key tag list into an actual RSA public key object
@@ -668,16 +675,15 @@
except:
log.debug("DKIM: Unable to make public key:\n%s" % (key_data,))
return None
-
-
+
+
def flushCache(self):
PublicKeyLookup.keyCache = {}
class PublicKeyLookup_DNSTXT(PublicKeyLookup):
-
-
+
method = Q_DNS
@@ -687,19 +693,18 @@
depend on the lookup method.
"""
return "%s._domainkey.%s" % (self.dkim_tags["s"], self.dkim_tags["d"],)
-
-
+
+
def _lookupKeys(self):
"""
Do the key lookup using the actual lookup method.
"""
raise NotImplementedError
-
+
class PublicKeyLookup_HTTP_WellKnown(PublicKeyLookup):
-
-
+
method = Q_HTTP
@@ -708,36 +713,35 @@
Get a token used to uniquely identify the key being looked up. Token format will
depend on the lookup method.
"""
-
+
host = ".".join(self.dkim_tags["d"].split(".")[-2:])
return "https://%s/.well-known/domainkey/%s/%s" % (host, self.dkim_tags["d"], self.dkim_tags["s"],)
-
-
+
+
@inlineCallbacks
def _lookupKeys(self):
"""
Do the key lookup using the actual lookup method.
"""
-
+
response = (yield getURL(self._getSelectorKey()))
if response is None or response.code / 100 != 2:
log.debug("DKIM: Failed http/well-known lookup: %s %s" % (self._getSelectorKey(), response,))
returnValue(())
-
+
ct = response.headers.getRawHeaders("content-type", ("bogus/type",))[0]
ct = ct.split(";", 1)
ct = ct[0].strip()
if ct not in ("text/plain",):
log.debug("DKIM: Failed http/well-known lookup: wrong content-type returned %s %s" % (self._getSelectorKey(), ct,))
returnValue(())
-
+
returnValue(tuple([DKIMUtils.extractTags(line) for line in response.data.splitlines()]))
class PublicKeyLookup_PrivateExchange(PublicKeyLookup):
-
-
+
method = Q_PRIVATE
directory = None
@@ -748,13 +752,13 @@
depend on the lookup method.
"""
return "%s#%s" % (self.dkim_tags["d"], self.dkim_tags["s"],)
-
-
+
+
def _lookupKeys(self):
"""
Key information is stored in a file, one record per line.
"""
-
+
# Check validity of paths
if PublicKeyLookup_PrivateExchange.directory is None:
log.debug("DKIM: Failed private-exchange lookup: no directory configured")
@@ -771,7 +775,7 @@
except IOError, e:
log.debug("DKIM: Failed private-exchange lookup: could not read %s %s" % (keyfile, e,))
return succeed(())
-
+
return succeed(tuple([DKIMUtils.extractTags(line) for line in keys.splitlines()]))
@@ -797,22 +801,22 @@
"""
Check that a valid key exists, create the TXT record format data and make the needed child resources.
"""
-
+
# Get data from file
try:
with open(pubkeyfile) as f:
key_data = f.read()
except IOError, e:
- log.error("DKIM: Unable to open the public key file: %s because of %s" % (pubkeyfile, e, ))
+ log.error("DKIM: Unable to open the public key file: %s because of %s" % (pubkeyfile, e,))
raise
-
+
# Make sure we can parse a valid public key
try:
rsa.PublicKey.load_pkcs1(key_data)
except:
log.error("DKIM: Invalid public key file: %s" % (pubkeyfile,))
raise
-
+
# Make the TXT record
key_data = "".join(key_data.strip().splitlines()[1:-1])
txt_data = "v=DKIM1; s=ischedule; p=%s" % (key_data,)
@@ -820,7 +824,7 @@
# Setup resource hierarchy
domainResource = SimpleResource(principalCollections=None, isdir=True, defaultACL=SimpleResource.allReadACL)
self.putChild(domain, domainResource)
-
+
selectorResource = SimpleDataResource(principalCollections=None, content_type=MimeType.fromString("text/plain"), data=txt_data, defaultACL=SimpleResource.allReadACL)
domainResource.putChild(selector, selectorResource)
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/resource.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/resource.py 2012-09-10 23:38:10 UTC (rev 9801)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/resource.py 2012-09-11 15:26:56 UTC (rev 9802)
@@ -48,32 +48,41 @@
self.parent = parent
self._newStore = store
+
def deadProperties(self):
if not hasattr(self, "_dead_properties"):
self._dead_properties = NonePropertyStore(self)
return self._dead_properties
+
def etag(self):
return succeed(None)
+
def checkPreconditions(self, request):
return None
+
def resourceType(self):
return davxml.ResourceType.ischeduleinbox
+
def contentType(self):
- return MimeType.fromString("text/html; charset=utf-8");
+ return MimeType.fromString("text/html; charset=utf-8")
+
def isCollection(self):
return False
+
def isCalendarCollection(self):
return False
+
def isPseudoCalendarCollection(self):
return False
+
def principalForCalendarUserAddress(self, address):
for principalCollection in self.principalCollections():
principal = principalCollection.principalForCalendarUserAddress(address)
@@ -81,6 +90,7 @@
return principal
return None
+
def render(self, request):
output = """<html>
<head>
@@ -95,6 +105,7 @@
response.headers.setHeader("content-type", MimeType("text", "html"))
return response
+
def http_GET(self, request):
"""
The iSchedule GET method.
@@ -111,11 +122,11 @@
"Invalid query parameter",
))
query = query[0]
-
+
query = {
"capabilities" : self.doCapabilities,
}.get(query, None)
-
+
if query is None:
raise HTTPError(StatusResponse(
responsecode.BAD_REQUEST,
@@ -124,12 +135,13 @@
return query(request)
+
def doCapabilities(self, request):
"""
Return a list of all timezones known to the server.
"""
- # Determine min/max date-time for iSchedule
+ # Determine min/max date-time for iSchedule
now = PyCalendarDateTime.getNowUTC()
minDateTime = PyCalendarDateTime(now.getYear(), 1, 1, 0, 0, 0, PyCalendarTimezone(utc=True))
minDateTime.offsetYear(-1)
@@ -137,7 +149,7 @@
maxDateTime.offsetYear(10)
result = ischedulexml.QueryResult(
-
+
ischedulexml.Capabilities(
ischedulexml.Versions(
ischedulexml.Version.fromString("1.0"),
@@ -162,8 +174,8 @@
),
ischedulexml.CalendarDataTypes(
ischedulexml.CalendarDataType(**{
- "content-type":"text/calendar",
- "version":"2.0",
+ "content-type": "text/calendar",
+ "version": "2.0",
}),
),
ischedulexml.Attachments(
@@ -179,6 +191,7 @@
)
return XMLResponse(responsecode.OK, result)
+
@inlineCallbacks
def http_POST(self, request):
"""
@@ -191,7 +204,7 @@
# Need a transaction to work with
txn = self._newStore.newTransaction("new transaction for Server To Server Inbox Resource")
request._newStoreTransaction = txn
-
+
# Do the POST processing treating this as a non-local schedule
try:
result = (yield scheduler.doSchedulingViaPOST(txn, use_request_headers=True))
@@ -206,9 +219,11 @@
# ACL
##
+
def supportedPrivileges(self, request):
return succeed(deliverSchedulePrivilegeSet)
+
def defaultAccessControlList(self):
privs = (
davxml.Privilege(davxml.Read()),
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/servers.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/servers.py 2012-09-10 23:38:10 UTC (rev 9801)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/servers.py 2012-09-11 15:26:56 UTC (rev 9802)
@@ -32,17 +32,20 @@
log = Logger()
+
+
class IScheduleServers(object):
-
+
_fileInfo = None
_xmlFile = None
_servers = None
_domainMap = None
-
+
def __init__(self):
-
+
self._loadConfig()
+
def _loadConfig(self):
if IScheduleServers._servers is None:
IScheduleServers._xmlFile = FilePath(
@@ -58,13 +61,15 @@
IScheduleServers._servers = parser.servers
self._mapDomains()
IScheduleServers._fileInfo = fileInfo
-
+
+
def _mapDomains(self):
IScheduleServers._domainMap = {}
for server in IScheduleServers._servers:
for domain in server.domains:
IScheduleServers._domainMap[domain] = server
-
+
+
def mapDomain(self, domain):
"""
Map a calendar user address domain to a suitable server that can
@@ -72,21 +77,23 @@
"""
return IScheduleServers._domainMap.get(domain)
-ELEMENT_SERVERS = "servers"
-ELEMENT_SERVER = "server"
-ELEMENT_URI = "uri"
-ELEMENT_AUTHENTICATION = "authentication"
-ATTRIBUTE_TYPE = "type"
-ATTRIBUTE_BASICAUTH = "basic"
-ELEMENT_USER = "user"
-ELEMENT_PASSWORD = "password"
-ELEMENT_ALLOW_REQUESTS_FROM = "allow-requests-from"
-ELEMENT_ALLOW_REQUESTS_TO = "allow-requests-to"
-ELEMENT_DOMAINS = "domains"
-ELEMENT_DOMAIN = "domain"
-ELEMENT_CLIENT_HOSTS = "hosts"
-ELEMENT_HOST = "host"
+ELEMENT_SERVERS = "servers"
+ELEMENT_SERVER = "server"
+ELEMENT_URI = "uri"
+ELEMENT_AUTHENTICATION = "authentication"
+ATTRIBUTE_TYPE = "type"
+ATTRIBUTE_BASICAUTH = "basic"
+ELEMENT_USER = "user"
+ELEMENT_PASSWORD = "password"
+ELEMENT_ALLOW_REQUESTS_FROM = "allow-requests-from"
+ELEMENT_ALLOW_REQUESTS_TO = "allow-requests-to"
+ELEMENT_DOMAINS = "domains"
+ELEMENT_DOMAIN = "domain"
+ELEMENT_CLIENT_HOSTS = "hosts"
+ELEMENT_HOST = "host"
+
+
class IScheduleServersParser(object):
"""
Server-to-server configuration file parser.
@@ -94,14 +101,16 @@
def __repr__(self):
return "<%s %r>" % (self.__class__.__name__, self.xmlFile)
+
def __init__(self, xmlFile):
self.servers = []
-
+
# Read in XML
_ignore_etree, servers_node = xmlutil.readXML(xmlFile.path, ELEMENT_SERVERS)
self._parseXML(servers_node)
-
+
+
def _parseXML(self, node):
"""
Parse the XML root node from the server-to-server configuration document.
@@ -112,7 +121,9 @@
if child.tag == ELEMENT_SERVER:
self.servers.append(IScheduleServerRecord())
self.servers[-1].parseXML(child)
-
+
+
+
class IScheduleServerRecord (object):
"""
Contains server-to-server details.
@@ -129,11 +140,12 @@
self.client_hosts = []
self.unNormalizeAddresses = True
self.moreHeaders = []
-
+
if uri:
self.uri = uri
self._parseDetails()
+
def parseXML(self, node):
for child in node.getchildren():
if child.tag == ELEMENT_URI:
@@ -150,14 +162,16 @@
self._parseList(child, ELEMENT_HOST, self.client_hosts)
else:
raise RuntimeError("[%s] Unknown attribute: %s" % (self.__class__, child.tag,))
-
+
self._parseDetails()
+
def _parseList(self, node, element_name, appendto):
for child in node.getchildren():
if child.tag == element_name:
appendto.append(child.text)
+
def _parseAuthentication(self, node):
if node.get(ATTRIBUTE_TYPE) != ATTRIBUTE_BASICAUTH:
return
@@ -167,9 +181,10 @@
user = child.text
elif child.tag == ELEMENT_PASSWORD:
password = child.text
-
+
self.authentication = ("basic", user, password,)
+
def _parseDetails(self):
# Extract scheme, host, port and path
if self.uri.startswith("http://"):
@@ -178,14 +193,14 @@
elif self.uri.startswith("https://"):
self.ssl = True
rest = self.uri[8:]
-
+
splits = rest.split("/", 1)
hostport = splits[0].split(":")
self.host = hostport[0]
if len(hostport) > 1:
self.port = int(hostport[1])
else:
- self.port = {False:80, True:443}[self.ssl]
+ self.port = {False: 80, True: 443}[self.ssl]
self.path = "/"
if len(splits) > 1:
self.path += splits[1]
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/test/test_dkim.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/test/test_dkim.py 2012-09-10 23:38:10 UTC (rev 9801)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/test/test_dkim.py 2012-09-11 15:26:56 UTC (rev 9802)
@@ -17,8 +17,8 @@
from twext.web2.http_headers import Headers, MimeType
from twext.web2.stream import MemoryStream
from twisted.internet.defer import inlineCallbacks, succeed
-from twistedcaldav.scheduling.ischedule.dkim import DKIMRequest, DKIMVerifier,\
- DKIMVerificationError, DKIMUtils, PublicKeyLookup_DNSTXT,\
+from twistedcaldav.scheduling.ischedule.dkim import DKIMRequest, DKIMVerifier, \
+ DKIMVerificationError, DKIMUtils, PublicKeyLookup_DNSTXT, \
PublicKeyLookup_HTTP_WellKnown, PublicKeyLookup_PrivateExchange
import base64
import hashlib
@@ -33,16 +33,16 @@
"""
class PublicKeyLookup_Testing(PublicKeyLookup_HTTP_WellKnown):
-
+
keys = []
-
+
def _lookupKeys(self):
"""
Do the key lookup using the actual lookup method.
"""
return succeed(self.keys)
-
+
def setUp(self):
super(TestDKIMBase, self).setUp()
@@ -94,6 +94,8 @@
f.close()
self.public_key_data = pkey_data.replace("\n", "")
+
+
class TestDKIMRequest (TestDKIMBase):
"""
L{DKIMRequest} support tests.
@@ -111,13 +113,13 @@
headers = Headers()
headers.addRawHeader("Originator", "mailto:user01 at example.com")
headers.addRawHeader("Recipient", "mailto:user02 at example.com")
- headers.setHeader("Content-Type", MimeType("text", "calendar", **{"component":"VEVENT", "charset":"utf-8"}))
+ headers.setHeader("Content-Type", MimeType("text", "calendar", **{"component": "VEVENT", "charset": "utf-8"}))
request = DKIMRequest("POST", "/", headers, stream, "example.com", "dkim", "/tmp/key", algorithm, ("Originator", "Recipient", "Content-Type",), True, True, True, 3600)
hash = base64.b64encode(hash_method(data).digest())
result = (yield request.bodyHash())
self.assertEqual(result, hash)
-
+
def test_generateSignature(self):
data = "Hello World!"
@@ -130,7 +132,7 @@
headers = Headers()
headers.addRawHeader("Originator", "mailto:user01 at example.com")
headers.addRawHeader("Recipient", "mailto:user02 at example.com")
- headers.setHeader("Content-Type", MimeType("text", "calendar", **{"component":"VEVENT", "charset":"utf-8"}))
+ headers.setHeader("Content-Type", MimeType("text", "calendar", **{"component": "VEVENT", "charset": "utf-8"}))
request = DKIMRequest("POST", "/", headers, stream, "example.com", "dkim", self.private_keyfile, algorithm, ("Originator", "Recipient", "Content-Type",), True, True, True, 3600)
# Manually create what should be the correct thing to sign
@@ -141,12 +143,12 @@
ischedule-version:1.0
dkim-signature:v=1; d=example.com; s=dkim; t=%s; x=%s; a=%s; q=dns/txt:http/well-known; http=UE9TVDov; c=relaxed/simple; h=Originator:Recipient; bh=%s; b=
""".replace("\n", "\r\n") % (headers.getRawHeaders("Content-Type")[0], str(int(time.time())), str(int(time.time() + 3600)), algorithm, bodyhash)
-
+
result = request.generateSignature(sign_this)
-
+
key = rsa.PrivateKey.load_pkcs1(open(self.private_keyfile).read())
signature = base64.b64encode(rsa.sign(sign_this, key, hash_name))
-
+
self.assertEqual(result, signature)
@@ -163,10 +165,10 @@
headers = Headers()
headers.addRawHeader("Originator", "mailto:user01 at example.com")
headers.addRawHeader("Recipient", "mailto:user02 at example.com")
- headers.setHeader("Content-Type", MimeType("text", "calendar", **{"component":"VEVENT", "charset":"utf-8"}))
+ headers.setHeader("Content-Type", MimeType("text", "calendar", **{"component": "VEVENT", "charset": "utf-8"}))
request = DKIMRequest("POST", "/", headers, stream, "example.com", "dkim", self.private_keyfile, algorithm, ("Originator", "Recipient", "Content-Type",), True, True, True, 3600)
result, _ignore_tags = (yield request.signatureHeaders())
-
+
# Manually create what should be the correct thing to sign
bodyhash = base64.b64encode(hash_method(data).digest())
sign_this = """originator:mailto:user01 at example.com
@@ -178,10 +180,10 @@
cache-control:no-transform
dkim-signature:v=1; d=example.com; s=dkim; t=%s; x=%s; a=%s; q=dns/txt:http/well-known:private-exchange; http=UE9TVDov; c=relaxed/simple; h=Originator:Recipient:Content-Type:iSchedule-Version:iSchedule-Message-ID:Cache-Control:Cache-Control; bh=%s; b=
""".replace("\n", "\r\n") % (headers.getRawHeaders("Content-Type")[0], request.message_id, request.time, request.expire, algorithm, bodyhash)
-
+
self.assertEqual(result, sign_this)
-
+
@inlineCallbacks
def test_sign(self):
@@ -194,10 +196,10 @@
headers = Headers()
headers.addRawHeader("Originator", "mailto:user01 at example.com")
headers.addRawHeader("Recipient", "mailto:user02 at example.com")
- headers.setHeader("Content-Type", MimeType("text", "calendar", **{"component":"VEVENT", "charset":"utf-8"}))
+ headers.setHeader("Content-Type", MimeType("text", "calendar", **{"component": "VEVENT", "charset": "utf-8"}))
request = DKIMRequest("POST", "/", headers, stream, "example.com", "dkim", self.private_keyfile, algorithm, ("Originator", "Recipient", "Content-Type",), True, True, True, 3600)
result = (yield request.sign())
-
+
# Manually create what should be the correct thing to sign and make sure signatures match
bodyhash = base64.b64encode(hash_method(data).digest())
sign_this = """originator:mailto:user01 at example.com
@@ -211,25 +213,26 @@
""".replace("\n", "\r\n") % (headers.getRawHeaders("Content-Type")[0], request.message_id, request.time, request.expire, algorithm, bodyhash)
key = rsa.PrivateKey.load_pkcs1(open(self.private_keyfile).read())
signature = base64.b64encode(rsa.sign(sign_this, key, hash_name))
-
+
self.assertEqual(result, signature)
-
+
# Make sure header is updated in the request
updated_header = "v=1; d=example.com; s=dkim; t=%s; x=%s; a=%s; q=dns/txt:http/well-known:private-exchange; http=UE9TVDov; c=relaxed/simple; h=Originator:Recipient:Content-Type:iSchedule-Version:iSchedule-Message-ID:Cache-Control:Cache-Control; bh=%s; b=%s" % (request.time, request.expire, algorithm, bodyhash, signature,)
- self.assertEqual(request.headers.getRawHeaders("DKIM-Signature")[0], updated_header)
+ self.assertEqual(request.headers.getRawHeaders("DKIM-Signature")[0], updated_header)
# Try to verify result using public key
pubkey = rsa.PublicKey.load_pkcs1(open(self.public_keyfile).read())
self.assertEqual(rsa.verify(sign_this, base64.b64decode(result), pubkey), None)
+
class TestDKIMVerifier (TestDKIMBase):
"""
L{DKIMVerifier} support tests.
"""
class StubRequest(object):
-
+
def __init__(self, method, uri, headers, body):
self.method = method
self.uri = uri
@@ -243,11 +246,11 @@
"""
L{DKIMVerifier.processDKIMHeader} correctly validates DKIM-Signature headers.
"""
-
+
data = (
# Bogus
((("DKIM-Signature", "v=1"),), False,),
-
+
# More than one
((
("DKIM-Signature", "v=1; d=example.com; s=dkim; t=1234; a=rsa-sha1; q=dns/txt:http/well-known; http=UE9TVDov; c=relaxed/simple; h=Originator:Recipient; bh=abc; b=def"),
@@ -269,7 +272,7 @@
((("DKIM-Signature", "v=1; d=example.com; s=dkim; t=1234; x=%d; a=rsa-sha256; q=dns/txt; http=UE9TVDovaXNjaGVkdWxl; c=relaxed; h=Originator:Recipient; bh=abc; b=def" % (int(time.time() - 30),)),), False,),
((("DKIM-Signature", "v=1; d=example.com; s=dkim; t=1234; x=%d; a=rsa-sha256; q=dns/txt; http=POST:/; c=relaxed; h=Originator:Recipient; bh=abc; b=def" % (int(time.time() - 30),)),), False,),
)
-
+
for headers, result in data:
request = self.StubRequest("POST", "/", headers, "")
verifier = DKIMVerifier(request)
@@ -283,7 +286,7 @@
"""
L{DKIMVerifier.canonicalizeHeader} correctly canonicalizes headers.
"""
-
+
data = (
("Content-Type", " text/calendar ; charset = \"utf-8\" ", "content-type:text/calendar ; charset = \"utf-8\"\r\n"),
("Originator", " mailto:user01 at example.com ", "originator:mailto:user01 at example.com\r\n"),
@@ -300,7 +303,7 @@
"dkim-signature:v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known ; b= ; http= UE9TVDov ; c=relaxed/simple; h=Originator:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc\r\n",
),
)
-
+
for name, value, result in data:
request = self.StubRequest("POST", "/", ((name, value,),), "")
verifier = DKIMVerifier(request)
@@ -314,13 +317,13 @@
"""
L{DKIMVerifier.extractSignedHeaders} correctly extracts canonicalizes headers.
"""
-
+
data = (
# Over count on Recipient
("""Host:example.com
-Content-Type: text/calendar ; charset = "utf-8"
-Originator: mailto:user01 at example.com
-Recipient: mailto:user02 at example.com ,\t mailto:user03 at example.com\t\t
+Content-Type: text/calendar ; charset = "utf-8"
+Originator: mailto:user01 at example.com
+Recipient: mailto:user02 at example.com ,\t mailto:user03 at example.com\t\t
iSchedule-Version: 1.0
DKIM-Signature: v=1;\t\t d=example.com; s = dkim; t\t=\t1234; a=rsa-sha1; \t\tq=dns/txt:http/well-known\t\t; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=def
Cache-Control:no-cache
@@ -335,10 +338,10 @@
),
# Exact count on Recipient
("""Host:example.com
-Content-Type: text/calendar ; charset = "utf-8"
-Originator: mailto:user01 at example.com
-Recipient: mailto:user02 at example.com ,\t mailto:user03 at example.com\t\t
-Recipient:\t\t mailto:user04 at example.com
+Content-Type: text/calendar ; charset = "utf-8"
+Originator: mailto:user01 at example.com
+Recipient: mailto:user02 at example.com ,\t mailto:user03 at example.com\t\t
+Recipient:\t\t mailto:user04 at example.com
iSchedule-Version: 1.0
DKIM-Signature: v=1;\t\t d=example.com; s = dkim; t\t=\t1234; a=rsa-sha1; \t\tq=dns/txt:http/well-known\t\t; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=def
Cache-Control:no-cache
@@ -354,12 +357,12 @@
),
# Under count on Recipient
("""Host:example.com
-Content-Type: text/calendar ; charset = "utf-8"
+Content-Type: text/calendar ; charset = "utf-8"
iSchedule-Version: 1.0
-Originator: mailto:user01 at example.com
-Recipient: mailto:user02 at example.com ,\t mailto:user03 at example.com\t\t
-Recipient:\t\t mailto:user04 at example.com
-Recipient:\t\t mailto:user05 at example.com
+Originator: mailto:user01 at example.com
+Recipient: mailto:user02 at example.com ,\t mailto:user03 at example.com\t\t
+Recipient:\t\t mailto:user04 at example.com
+Recipient:\t\t mailto:user05 at example.com
DKIM-Signature: v=1;\t\t d=example.com; s = dkim; t\t=\t1234; a=rsa-sha1; \t\tq=dns/txt:http/well-known\t\t; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=def
Cache-Control:no-cache
Connection:close
@@ -375,10 +378,10 @@
# Re-ordered Content-Type
("""Host:example.com
iSchedule-Version: 1.0
-Originator: mailto:user01 at example.com
-Recipient: mailto:user02 at example.com ,\t mailto:user03 at example.com\t\t
+Originator: mailto:user01 at example.com
+Recipient: mailto:user02 at example.com ,\t mailto:user03 at example.com\t\t
DKIM-Signature: v=1;\t\t d=example.com; s = dkim; t\t=\t1234; a=rsa-sha1; \t\tq=dns/txt:http/well-known\t\t; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=def
-Content-Type: text/calendar ; charset = "utf-8"
+Content-Type: text/calendar ; charset = "utf-8"
Cache-Control:no-cache
Connection:close
""",
@@ -390,7 +393,7 @@
"""
),
)
-
+
for hdrs, result in data:
headers = [hdr.split(":", 1) for hdr in hdrs.splitlines()]
request = self.StubRequest("POST", "/", headers, "")
@@ -404,13 +407,13 @@
"""
L{DKIMVerifier.locatePublicKey} correctly finds key matching headers.
"""
-
+
data = (
# Valid
("""Host:example.com
-Content-Type: text/calendar ; charset = "utf-8"
-Originator: mailto:user01 at example.com
-Recipient: mailto:user02 at example.com ,\t mailto:user03 at example.com\t\t
+Content-Type: text/calendar ; charset = "utf-8"
+Originator: mailto:user01 at example.com
+Recipient: mailto:user02 at example.com ,\t mailto:user03 at example.com\t\t
DKIM-Signature: v=1;\t\t d=example.com; s = dkim; t\t=\t1234; a=rsa-sha1; \t\tq=dns/txt:http/well-known\t\t; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=def
Cache-Control:no-cache
Connection:close
@@ -420,9 +423,9 @@
),
# Invalid - no method
("""Host:example.com
-Content-Type: text/calendar ; charset = "utf-8"
-Originator: mailto:user01 at example.com
-Recipient: mailto:user02 at example.com ,\t mailto:user03 at example.com\t\t
+Content-Type: text/calendar ; charset = "utf-8"
+Originator: mailto:user01 at example.com
+Recipient: mailto:user02 at example.com ,\t mailto:user03 at example.com\t\t
DKIM-Signature: v=1;\t\t d=example.com; s = dkim; t\t=\t1234; a=rsa-sha1; \t\tq=dns/txt\t\t; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=def
Cache-Control:no-cache
Connection:close
@@ -432,9 +435,9 @@
),
# Invalid - wrong algorithm
("""Host:example.com
-Content-Type: text/calendar ; charset = "utf-8"
-Originator: mailto:user01 at example.com
-Recipient: mailto:user02 at example.com ,\t mailto:user03 at example.com\t\t
+Content-Type: text/calendar ; charset = "utf-8"
+Originator: mailto:user01 at example.com
+Recipient: mailto:user02 at example.com ,\t mailto:user03 at example.com\t\t
DKIM-Signature: v=1;\t\t d=example.com; s = dkim; t\t=\t1234; a=rsa-sha1; \t\tq=dns/txt:http/well-known\t\t; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=def
Cache-Control:no-cache
Connection:close
@@ -443,7 +446,7 @@
False,
),
)
-
+
for hdrs, keys, result in data:
headers = [hdr.split(":", 1) for hdr in hdrs.splitlines()]
request = self.StubRequest("POST", "/", headers, "")
@@ -462,7 +465,7 @@
"""
L{DKIMVerifier.verify} correctly finds key matching headers.
"""
-
+
@inlineCallbacks
def _verify(hdrs, body, keys, result, sign_headers=("Originator", "Recipient", "Content-Type",), manipulate_request=None):
for algorithm in ("rsa-sha1", "rsa-sha256",):
@@ -473,11 +476,11 @@
headers.addRawHeader(name, value)
request = DKIMRequest("POST", "/", headers, stream, "example.com", "dkim", self.private_keyfile, algorithm, sign_headers, True, True, True, 3600)
yield request.sign()
-
+
# Possibly munge the request after the signature is done
if manipulate_request is not None:
manipulate_request(request)
-
+
# Verify signature
TestPublicKeyLookup.PublicKeyLookup_Testing.keys = keys
verifier = DKIMVerifier(request, key_lookup=(TestPublicKeyLookup.PublicKeyLookup_Testing,))
@@ -494,9 +497,9 @@
# Valid
yield _verify(
"""Host:example.com
-Content-Type: text/calendar ; charset = "utf-8"
-Originator: mailto:user01 at example.com
-Recipient: mailto:user02 at example.com ,\t mailto:user03 at example.com\t\t
+Content-Type: text/calendar ; charset = "utf-8"
+Originator: mailto:user01 at example.com
+Recipient: mailto:user02 at example.com ,\t mailto:user03 at example.com\t\t
Cache-Control:no-cache
Connection:close
""",
@@ -510,9 +513,9 @@
# Invalid - key revoked
yield _verify(
"""Host:example.com
-Content-Type: text/calendar ; charset = "utf-8"
-Originator: mailto:user01 at example.com
-Recipient: mailto:user02 at example.com ,\t mailto:user03 at example.com\t\t
+Content-Type: text/calendar ; charset = "utf-8"
+Originator: mailto:user01 at example.com
+Recipient: mailto:user02 at example.com ,\t mailto:user03 at example.com\t\t
Cache-Control:no-cache
Connection:close
""",
@@ -526,9 +529,9 @@
# Invalid - missing header
yield _verify(
"""Host:example.com
-Content-Type: text/calendar ; charset = "utf-8"
-Originator: mailto:user01 at example.com
-Recipient: mailto:user02 at example.com ,\t mailto:user03 at example.com\t\t
+Content-Type: text/calendar ; charset = "utf-8"
+Originator: mailto:user01 at example.com
+Recipient: mailto:user02 at example.com ,\t mailto:user03 at example.com\t\t
Cache-Control:no-cache
Connection:close
""",
@@ -537,15 +540,15 @@
""",
[DKIMUtils.extractTags("v=DKIM1; p=%s" % (self.public_key_data,))],
False,
- manipulate_request=lambda request:request.headers.removeHeader("Originator")
+ manipulate_request=lambda request: request.headers.removeHeader("Originator")
)
# Invalid - changed header
yield _verify(
"""Host:example.com
-Content-Type: text/calendar ; charset = "utf-8"
-Originator: mailto:user01 at example.com
-Recipient: mailto:user02 at example.com ,\t mailto:user03 at example.com\t\t
+Content-Type: text/calendar ; charset = "utf-8"
+Originator: mailto:user01 at example.com
+Recipient: mailto:user02 at example.com ,\t mailto:user03 at example.com\t\t
Cache-Control:no-cache
Connection:close
""",
@@ -554,15 +557,15 @@
""",
[DKIMUtils.extractTags("v=DKIM1; p=%s" % (self.public_key_data,))],
False,
- manipulate_request=lambda request:request.headers.setRawHeaders("Originator", ("mailto:user04 at example.com",))
+ manipulate_request=lambda request: request.headers.setRawHeaders("Originator", ("mailto:user04 at example.com",))
)
# Invalid - changed body
yield _verify(
"""Host:example.com
-Content-Type: text/calendar ; charset = "utf-8"
-Originator: mailto:user01 at example.com
-Recipient: mailto:user02 at example.com ,\t mailto:user03 at example.com\t\t
+Content-Type: text/calendar ; charset = "utf-8"
+Originator: mailto:user01 at example.com
+Recipient: mailto:user02 at example.com ,\t mailto:user03 at example.com\t\t
Cache-Control:no-cache
Connection:close
""",
@@ -571,15 +574,15 @@
""",
[DKIMUtils.extractTags("v=DKIM1; p=%s" % (self.public_key_data,))],
False,
- manipulate_request=lambda request:setattr(request, "stream", MemoryStream("BEGIN:DATA\n")),
+ manipulate_request=lambda request: setattr(request, "stream", MemoryStream("BEGIN:DATA\n")),
)
# Valid - extra header no over sign
yield _verify(
"""Host:example.com
-Content-Type: text/calendar ; charset = "utf-8"
-Originator: mailto:user01 at example.com
-Recipient: mailto:user02 at example.com ,\t mailto:user03 at example.com\t\t
+Content-Type: text/calendar ; charset = "utf-8"
+Originator: mailto:user01 at example.com
+Recipient: mailto:user02 at example.com ,\t mailto:user03 at example.com\t\t
Cache-Control:no-cache
Connection:close
""",
@@ -588,15 +591,15 @@
""",
[DKIMUtils.extractTags("v=DKIM1; p=%s" % (self.public_key_data,))],
True,
- manipulate_request=lambda request:request.headers.addRawHeader("Recipient", ("mailto:user04 at example.com",))
+ manipulate_request=lambda request: request.headers.addRawHeader("Recipient", ("mailto:user04 at example.com",))
)
# Valid - over sign header
yield _verify(
"""Host:example.com
-Content-Type: text/calendar ; charset = "utf-8"
-Originator: mailto:user01 at example.com
-Recipient: mailto:user02 at example.com ,\t mailto:user03 at example.com\t\t
+Content-Type: text/calendar ; charset = "utf-8"
+Originator: mailto:user01 at example.com
+Recipient: mailto:user02 at example.com ,\t mailto:user03 at example.com\t\t
Cache-Control:no-cache
Connection:close
""",
@@ -611,9 +614,9 @@
# Invalid - over sign header extra header
yield _verify(
"""Host:example.com
-Content-Type: text/calendar ; charset = "utf-8"
-Originator: mailto:user01 at example.com
-Recipient: mailto:user02 at example.com ,\t mailto:user03 at example.com\t\t
+Content-Type: text/calendar ; charset = "utf-8"
+Originator: mailto:user01 at example.com
+Recipient: mailto:user02 at example.com ,\t mailto:user03 at example.com\t\t
Cache-Control:no-cache
Connection:close
""",
@@ -623,7 +626,7 @@
[DKIMUtils.extractTags("v=DKIM1; p=%s" % (self.public_key_data,))],
False,
sign_headers=("Originator", "Recipient", "Recipient", "Content-Type",),
- manipulate_request=lambda request:request.headers.addRawHeader("Recipient", ("mailto:user04 at example.com",))
+ manipulate_request=lambda request: request.headers.addRawHeader("Recipient", ("mailto:user04 at example.com",))
)
@@ -634,7 +637,7 @@
"""
def test_selector_key(self):
-
+
for lookup, d, result in (
(PublicKeyLookup_DNSTXT, "example.com", "dkim._domainkey.example.com"),
(PublicKeyLookup_DNSTXT, "calendar.example.com", "dkim._domainkey.calendar.example.com"),
@@ -646,11 +649,11 @@
dkim = "v=1; d=%s; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known:private-exchange ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b=" % (d,)
tester = lookup(DKIMUtils.extractTags(dkim))
self.assertEqual(tester._getSelectorKey(), result)
-
-
+
+
@inlineCallbacks
def test_get_key(self):
-
+
# Valid
dkim = "v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known:private-exchange ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b="
lookup = TestPublicKeyLookup.PublicKeyLookup_Testing(DKIMUtils.extractTags(dkim))
@@ -658,7 +661,7 @@
lookup.keys = [DKIMUtils.extractTags("v=DKIM1; p=%s" % (self.public_key_data,))]
pubkey = (yield lookup.getPublicKey())
self.assertNotEqual(pubkey, None)
-
+
# Valid with more tags
dkim = "v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known:private-exchange ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b="
lookup = TestPublicKeyLookup.PublicKeyLookup_Testing(DKIMUtils.extractTags(dkim))
@@ -666,7 +669,7 @@
lookup.keys = [DKIMUtils.extractTags("v=DKIM1; k = rsa ; h= sha1 : sha256 ; s=ischedule ; p=%s" % (self.public_key_data,))]
pubkey = (yield lookup.getPublicKey())
self.assertNotEqual(pubkey, None)
-
+
# Invalid - key type
dkim = "v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known:private-exchange ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b="
lookup = TestPublicKeyLookup.PublicKeyLookup_Testing(DKIMUtils.extractTags(dkim))
@@ -674,7 +677,7 @@
lookup.keys = [DKIMUtils.extractTags("v=DKIM1; k=dsa ; p=%s" % (self.public_key_data,))]
pubkey = (yield lookup.getPublicKey())
self.assertEqual(pubkey, None)
-
+
# Invalid - hash
dkim = "v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known:private-exchange ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b="
lookup = TestPublicKeyLookup.PublicKeyLookup_Testing(DKIMUtils.extractTags(dkim))
@@ -682,7 +685,7 @@
lookup.keys = [DKIMUtils.extractTags("v=DKIM1; k=rsa ; h=sha512 ; p=%s" % (self.public_key_data,))]
pubkey = (yield lookup.getPublicKey())
self.assertEqual(pubkey, None)
-
+
# Invalid - service
dkim = "v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known:private-exchange ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b="
lookup = TestPublicKeyLookup.PublicKeyLookup_Testing(DKIMUtils.extractTags(dkim))
@@ -690,7 +693,7 @@
lookup.keys = [DKIMUtils.extractTags("v=DKIM1; k=rsa ; s=email ; p=%s" % (self.public_key_data,))]
pubkey = (yield lookup.getPublicKey())
self.assertEqual(pubkey, None)
-
+
# Invalid - revoked
dkim = "v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known:private-exchange ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b="
lookup = TestPublicKeyLookup.PublicKeyLookup_Testing(DKIMUtils.extractTags(dkim))
@@ -698,7 +701,7 @@
lookup.keys = [DKIMUtils.extractTags("v=DKIM1; k=rsa ; s=email ; p=")]
pubkey = (yield lookup.getPublicKey())
self.assertEqual(pubkey, None)
-
+
# Multiple valid
dkim = "v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known:private-exchange ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b="
lookup = TestPublicKeyLookup.PublicKeyLookup_Testing(DKIMUtils.extractTags(dkim))
@@ -710,7 +713,7 @@
]
pubkey = (yield lookup.getPublicKey())
self.assertNotEqual(pubkey, None)
-
+
# Multiple - some valid, some invalid
dkim = "v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known:private-exchange ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b="
lookup = TestPublicKeyLookup.PublicKeyLookup_Testing(DKIMUtils.extractTags(dkim))
@@ -723,7 +726,7 @@
]
pubkey = (yield lookup.getPublicKey())
self.assertNotEqual(pubkey, None)
-
+
# Multiple - invalid
dkim = "v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known:private-exchange ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b="
lookup = TestPublicKeyLookup.PublicKeyLookup_Testing(DKIMUtils.extractTags(dkim))
@@ -734,11 +737,11 @@
]
pubkey = (yield lookup.getPublicKey())
self.assertEqual(pubkey, None)
-
-
+
+
@inlineCallbacks
def test_cached_key(self):
-
+
# Create cache entry
dkim = "v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known:private-exchange ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b="
lookup = TestPublicKeyLookup.PublicKeyLookup_Testing(DKIMUtils.extractTags(dkim))
@@ -746,14 +749,14 @@
lookup.keys = [DKIMUtils.extractTags("v=DKIM1; p=%s" % (self.public_key_data,))]
pubkey = (yield lookup.getPublicKey())
self.assertNotEqual(pubkey, None)
-
+
# Cache valid
dkim = "v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known:private-exchange ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b="
lookup = TestPublicKeyLookup.PublicKeyLookup_Testing(DKIMUtils.extractTags(dkim))
lookup.keys = []
pubkey = (yield lookup.getPublicKey())
self.assertNotEqual(pubkey, None)
-
+
# Cache invalid
dkim = "v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known:private-exchange ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b="
lookup = TestPublicKeyLookup.PublicKeyLookup_Testing(DKIMUtils.extractTags(dkim))
@@ -761,10 +764,11 @@
lookup.keys = []
pubkey = (yield lookup.getPublicKey())
self.assertEqual(pubkey, None)
-
+
+
@inlineCallbacks
def test_private_exchange(self):
-
+
keydir = self.mktemp()
PublicKeyLookup_PrivateExchange.directory = keydir
os.mkdir(keydir)
@@ -772,12 +776,12 @@
with open(keyfile, "w") as f:
f.write("""v=DKIM1; p=%s
""" % (self.public_key_data,))
-
+
dkim = "v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known:private-exchange ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b="
lookup = PublicKeyLookup_PrivateExchange(DKIMUtils.extractTags(dkim))
pubkey = (yield lookup.getPublicKey())
self.assertNotEqual(pubkey, None)
-
+
dkim = "v=1; d=example.com; s = dkim2; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known:private-exchange ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b="
lookup = PublicKeyLookup_PrivateExchange(DKIMUtils.extractTags(dkim))
lookup.flushCache()
@@ -787,7 +791,7 @@
with open(keyfile, "w") as f:
f.write("""v=DKIM1; s=email; p=%s
""" % (self.public_key_data,))
-
+
dkim = "v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known:private-exchange ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b="
lookup = PublicKeyLookup_PrivateExchange(DKIMUtils.extractTags(dkim))
lookup.flushCache()
@@ -798,7 +802,7 @@
f.write("""v=DKIM1; s=email; p=%s
v=DKIM1; s=ischedule; p=%s
""" % (self.public_key_data, self.public_key_data,))
-
+
dkim = "v=1; d=example.com; s = dkim; t = 1234; a=rsa-sha1; q=dns/txt:http/well-known:private-exchange ; http=UE9TVDov; c=relaxed/simple; h=Content-Type:Originator:Recipient:Recipient:iSchedule-Version:iSchedule-Message-ID; bh=abc; b="
lookup = PublicKeyLookup_PrivateExchange(DKIMUtils.extractTags(dkim))
lookup.flushCache()
Modified: CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/xml.py
===================================================================
--- CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/xml.py 2012-09-10 23:38:10 UTC (rev 9801)
+++ CalendarServer/branches/users/cdaboo/ischedule-dkim/twistedcaldav/scheduling/ischedule/xml.py 2012-09-11 15:26:56 UTC (rev 9802)
@@ -38,6 +38,7 @@
}
+
@registerElement
class Capabilities (WebDAVElement):
namespace = ischedule_namespace
@@ -57,6 +58,7 @@
}
+
@registerElement
class Versions (WebDAVElement):
namespace = ischedule_namespace
@@ -66,12 +68,14 @@
}
+
@registerElement
class Version (WebDAVTextElement):
namespace = ischedule_namespace
name = "version"
+
@registerElement
class SchedulingMessages (WebDAVElement):
namespace = ischedule_namespace
@@ -81,6 +85,7 @@
}
+
@registerElement
class Component (WebDAVElement):
namespace = ischedule_namespace
@@ -88,16 +93,18 @@
allowed_children = {
(ischedule_namespace, "method"): (0, None),
}
- allowed_attributes = { "name": True }
+ allowed_attributes = {"name": True}
+
@registerElement
class Method (WebDAVEmptyElement):
namespace = ischedule_namespace
name = "method"
- allowed_attributes = { "name": True }
+ allowed_attributes = {"name": True}
+
@registerElement
class CalendarDataTypes (WebDAVElement):
namespace = ischedule_namespace
@@ -107,6 +114,7 @@
}
+
@registerElement
class CalendarDataType (WebDAVTextElement):
namespace = ischedule_namespace
@@ -117,6 +125,7 @@
}
+
@registerElement
class Attachments (WebDAVElement):
namespace = ischedule_namespace
@@ -127,50 +136,57 @@
}
+
@registerElement
class Inline (WebDAVEmptyElement):
namespace = ischedule_namespace
name = "inline"
+
@registerElement
class External (WebDAVEmptyElement):
namespace = ischedule_namespace
name = "external"
+
@registerElement
class MaxContentLength (WebDAVTextElement):
namespace = ischedule_namespace
name = "max-content-length"
+
@registerElement
class MinDateTime (WebDAVTextElement):
namespace = ischedule_namespace
name = "min-date-time"
+
@registerElement
class MaxDateTime (WebDAVTextElement):
namespace = ischedule_namespace
name = "max-date-time"
+
@registerElement
class MaxInstances (WebDAVTextElement):
namespace = ischedule_namespace
name = "max-instances"
+
@registerElement
class MaxRecipients (WebDAVTextElement):
namespace = ischedule_namespace
name = "max-recipients"
+
@registerElement
class Administrator (WebDAVTextElement):
namespace = ischedule_namespace
name = "administrator"
-
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20120911/cc0d8564/attachment-0001.html>
More information about the calendarserver-changes
mailing list