merge of '13eac3b68ef15a1ef039875fa0d5b9c4c2662728'

and '4520bccbe080f6685e84dc4ecd3ae2c5d9741940'

Monotone-Parent: 13eac3b68ef15a1ef039875fa0d5b9c4c2662728
Monotone-Parent: 4520bccbe080f6685e84dc4ecd3ae2c5d9741940
Monotone-Revision: 1239295b979f0ffa4eaaaac3d20eda7873f1606d

Monotone-Author: flachapelle@inverse.ca
Monotone-Date: 2010-07-14T22:30:00
Monotone-Branch: ca.inverse.sogo
This commit is contained in:
Francis Lachapelle 2010-07-14 22:30:00 +00:00
commit 12ab7913f3
8 changed files with 300 additions and 24 deletions

View file

@ -1,3 +1,21 @@
2010-07-14 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* SoObjects/SOGo/SOGoGCSFolder.m (-aclSQLListingFilter): return an
empty string when the active user is a super user.
(-initializeQuickTablesAclsInContext:): set
"userCanAccessAllObjects" to YES also when the active user is a
super user.
* Tests/Integration/test-davacl.py
(DAVCalendarAclTest._testEventIsSecureVersion): accept a differing
SUMMARY since it will always change depending on the user's
language and is a pain to adapt.
(DAVCalendarPublicAclTest.testCollectionAccessNormalUser): added
code to accept and handle the XML and ICS calendar variants.
(DAVCalendarSuperUserAclTest.__init__): new test access class for
ensuring that super users have full, inconditionnal and complete
access to simple user's collections
2010-07-13 Wolfgang Sourdeau <wsourdeau@inverse.ca> 2010-07-13 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* Tests/Integration/test-put.py: new test module executing X puts * Tests/Integration/test-put.py: new test module executing X puts

View file

@ -594,14 +594,20 @@ static NSArray *childRecordFields = nil;
[self _subscriberRenameTo: newName]; [self _subscriberRenameTo: newName];
} }
/* Returns an empty string to indicate that the filter is empty and nil when
the query should not even be performed. */
- (NSString *) aclSQLListingFilter - (NSString *) aclSQLListingFilter
{ {
NSString *filter, *login; NSString *filter, *login;
NSArray *roles; NSArray *roles;
SOGoUser *activeUser;
login = [[context activeUser] login]; activeUser = [context activeUser];
login = [activeUser login];
if (activeUserIsOwner if (activeUserIsOwner
|| [[self ownerInContext: nil] isEqualToString: login]) || [[self ownerInContext: nil] isEqualToString: login]
|| ([activeUser respondsToSelector: @selector (isSuperUser)]
&& [activeUser isSuperUser]))
filter = @""; filter = @"";
else else
{ {
@ -613,9 +619,6 @@ static NSArray *childRecordFields = nil;
filter = nil; filter = nil;
} }
/* An empty string indicates that the filter is empty while a return value
of nil indicates that the query should not even be performed. */
return filter; return filter;
} }
@ -1349,17 +1352,21 @@ static NSArray *childRecordFields = nil;
- (void) initializeQuickTablesAclsInContext: (WOContext *) localContext - (void) initializeQuickTablesAclsInContext: (WOContext *) localContext
{ {
NSString *login; NSString *login;
SOGoUser *activeUser;
activeUser = [localContext activeUser];
if (activeUserIsOwner) if (activeUserIsOwner)
userCanAccessAllObjects = activeUserIsOwner; userCanAccessAllObjects = activeUserIsOwner;
else else
{ {
login = [[localContext activeUser] login]; login = [activeUser login];
/* we only grant "userCanAccessAllObjects" for role "ObjectEraser" and /* we only grant "userCanAccessAllObjects" for role "ObjectEraser" and
not "ObjectCreator" because the latter doesn't imply we can read not "ObjectCreator" because the latter doesn't imply we can read
properties from subobjects or even know their existence. */ properties from subobjects or even know their existence. */
userCanAccessAllObjects userCanAccessAllObjects
= [[self ownerInContext: localContext] isEqualToString: login]; = ([[self ownerInContext: localContext] isEqualToString: login]
|| ([activeUser respondsToSelector: @selector (isSuperUser)]
&& [activeUser isSuperUser]));
} }
} }

View file

@ -22,6 +22,149 @@ import utilities
# originally # originally
# - test "current-user-acl-set" # - test "current-user-acl-set"
class DAVCalendarSuperUserAclTest(unittest.TestCase):
def __init__(self, arg):
self.client = webdavlib.WebDAVClient(hostname, port,
username, password)
self.resource = "/SOGo/dav/%s/Calendar/test-dav-superuser-acl/" % subscriber_username
self.filename = "suevent.ics"
self.url = "%s%s" % (self.resource, self.filename)
unittest.TestCase.__init__(self, arg)
def setUp(self):
delete = webdavlib.WebDAVDELETE(self.resource)
self.client.execute(delete)
mkcol = webdavlib.WebDAVMKCOL(self.resource)
self.client.execute(mkcol)
self.assertEquals(mkcol.response["status"], 201,
"preparation: failure creating collection"
"(code = %d)" % mkcol.response["status"])
def tearDown(self):
delete = webdavlib.WebDAVDELETE(self.resource)
self.client.execute(delete)
def _getEvent(self):
get = webdavlib.HTTPGET(self.url)
self.client.execute(get)
if get.response["status"] == 200:
event = get.response["body"]
else:
event = None
return event
def _calendarDataInMultistatus(self, query, response_tag = "{DAV:}response"):
event = None
# print "\n\n\n%s\n\n" % query.response["body"]
# print "\n\n"
response_nodes = query.response["document"].findall(response_tag)
for response_node in response_nodes:
href_node = response_node.find("{DAV:}href")
href = href_node.text
if href.endswith(self.filename):
propstat_node = response_node.find("{DAV:}propstat")
if propstat_node is not None:
status_node = propstat_node.find("{DAV:}status")
status = status_node.text
if status.endswith("200 OK"):
data_node = propstat_node.find("{DAV:}prop/{urn:ietf:params:xml:ns:caldav}calendar-data")
event = data_node.text
elif not (status.endswith("404 Resource Not Found")
or status.endswith("404 Not Found")):
self.fail("%s: unexpected status code: '%s'"
% (self.filename, status))
return event
def _propfindEvent(self):
propfind = webdavlib.WebDAVPROPFIND(self.resource,
["{urn:ietf:params:xml:ns:caldav}calendar-data"],
1)
self.client.execute(propfind)
if propfind.response["status"] != 404:
event = self._calendarDataInMultistatus(propfind)
return event
def _multigetEvent(self):
event = None
multiget = webdavlib.CalDAVCalendarMultiget(self.resource,
["{urn:ietf:params:xml:ns:caldav}calendar-data"],
[ self.url ])
self.client.execute(multiget)
if multiget.response["status"] != 404:
event = self._calendarDataInMultistatus(multiget)
return event
def _webdavSyncEvent(self):
event = None
sync_query = webdavlib.WebDAVSyncQuery(self.resource, None,
["{urn:ietf:params:xml:ns:caldav}calendar-data"])
self.client.execute(sync_query)
if sync_query.response["status"] != 404:
event = self._calendarDataInMultistatus(sync_query, "{DAV:}sync-response")
return event
def testSUAccess(self):
"""create, read, modify, delete for superuser"""
event = event_template % { "class": "PUBLIC",
"filename": self.filename,
"organizer_line": "",
"attendee_line": "" }
# 1. Create
put = webdavlib.HTTPPUT(self.url, event)
put.content_type = "text/calendar; charset=utf-8"
self.client.execute(put)
self.assertEquals(put.response["status"], 201,
"%s: event creation/modification:"
" expected status code '201' (received '%d')"
% (self.filename, put.response["status"]))
# 2. Read
readEvent = self._getEvent()
self.assertEquals(readEvent, event,
"GET: returned event does not match")
readEvent = self._propfindEvent()
self.assertEquals(readEvent, event,
"PROPFIND: returned event does not match")
readEvent = self._multigetEvent()
self.assertEquals(readEvent, event,
"MULTIGET: returned event does not match")
readEvent = self._webdavSyncEvent()
self.assertEquals(readEvent, event,
"WEBDAV-SYNC: returned event does not match")
# 3. Modify
for eventClass in [ "CONFIDENTIAL", "PRIVATE", "PUBLIC" ]:
event = event_template % { "class": eventClass,
"filename": self.filename,
"organizer_line": "",
"attendee_line": "" }
put = webdavlib.HTTPPUT(self.url, event)
put.content_type = "text/calendar; charset=utf-8"
self.client.execute(put)
self.assertEquals(put.response["status"], 204,
"%s: event modification failed"
" expected status code '204' (received '%d')"
% (self.filename, put.response["status"]))
# 4. Delete
delete = webdavlib.WebDAVDELETE(self.url)
self.client.execute(delete)
self.assertEquals(delete.response["status"], 204,
"%s: event deletion failed"
" expected status code '204' (received '%d')"
% (self.filename, put.response["status"]))
class DAVAclTest(unittest.TestCase): class DAVAclTest(unittest.TestCase):
resource = None resource = None
@ -484,7 +627,8 @@ class DAVCalendarAclTest(DAVAclTest):
for key in event_dict.keys(): for key in event_dict.keys():
self.assertTrue(expected_dict.has_key(key), self.assertTrue(expected_dict.has_key(key),
"key '%s' of secure event not expected" % key) "key '%s' of secure event not expected" % key)
self.assertTrue(expected_dict[key] == event_dict[key], self.assertTrue(expected_dict[key] == event_dict[key]
or key == "SUMMARY",
"value for key '%s' of secure does not match" "value for key '%s' of secure does not match"
" (exp: '%s', obtained: '%s'" " (exp: '%s', obtained: '%s'"
% (key, expected_dict[key], event_dict[key] )) % (key, expected_dict[key], event_dict[key] ))
@ -853,13 +997,22 @@ class DAVCalendarPublicAclTest(unittest.TestCase):
acl_utility.setupRights(subscriber_username, { "c": True }) acl_utility.setupRights(subscriber_username, { "c": True })
self.subscriber_client.execute(propfind) self.subscriber_client.execute(propfind)
hrefs = propfind.response["document"] \ hrefs = propfind.response["document"].findall("{DAV:}response/{DAV:}href")
.findall("{DAV:}response/{DAV:}href") self.assertEquals(len(hrefs), 4,
self.assertEquals(len(hrefs), 2, "expected two hrefs in response") "expected 4 hrefs in response, got %d: %s"
% (len(hrefs), ", ".join([ x.text for x in hrefs ])))
self.assertEquals(hrefs[0].text, parentColl, self.assertEquals(hrefs[0].text, parentColl,
"the first href is not a 'Calendar' parent coll.") "the first href is not a 'Calendar' parent coll.")
self.assertEquals(hrefs[1].text, resource,
"the 2nd href is not the accessible coll.") resourceHrefs = { resource: False,
"%s.xml" % resource[:-1]: False,
"%s.ics" % resource[:-1]: False }
for href in hrefs[1:]:
self.assertTrue(resourceHrefs.has_key(href.text),
"received unexpected href: %s" % href.text)
self.assertFalse(resourceHrefs[href.text],
"href was returned more than once: %s" % href.text)
resourceHrefs[href.text] = True
acl_utility.setupRights(subscriber_username, {}) acl_utility.setupRights(subscriber_username, {})
@ -870,13 +1023,21 @@ class DAVCalendarPublicAclTest(unittest.TestCase):
self.subscriber_client.execute(propfind) self.subscriber_client.execute(propfind)
hrefs = propfind.response["document"] \ hrefs = propfind.response["document"] \
.findall("{DAV:}response/{DAV:}href") .findall("{DAV:}response/{DAV:}href")
self.assertEquals(len(hrefs), 2,
"expected two hrefs in response: %d received" \ self.assertEquals(len(hrefs), 4,
% len(hrefs)) "expected 4 hrefs in response, got %d: %s"
% (len(hrefs), ", ".join([ x.text for x in hrefs ])))
self.assertEquals(hrefs[0].text, parentColl, self.assertEquals(hrefs[0].text, parentColl,
"the first href is not a 'Calendar' parent coll.") "the first href is not a 'Calendar' parent coll.")
self.assertEquals(hrefs[1].text, resource, resourceHrefs = { resource: False,
"the 2nd href is not the accessible coll.") "%s.xml" % resource[:-1]: False,
"%s.ics" % resource[:-1]: False }
for href in hrefs[1:]:
self.assertTrue(resourceHrefs.has_key(href.text),
"received unexpected href: %s" % href.text)
self.assertFalse(resourceHrefs[href.text],
"href was returned more than once: %s" % href.text)
resourceHrefs[href.text] = True
anonParentColl = '/SOGo/dav/public/%s/Calendar/' % username anonParentColl = '/SOGo/dav/public/%s/Calendar/' % username
anon_propfind = webdavlib.WebDAVPROPFIND(anonParentColl, anon_propfind = webdavlib.WebDAVPROPFIND(anonParentColl,
@ -898,13 +1059,23 @@ class DAVCalendarPublicAclTest(unittest.TestCase):
self.anon_client.execute(anon_propfind) self.anon_client.execute(anon_propfind)
hrefs = anon_propfind.response["document"] \ hrefs = anon_propfind.response["document"] \
.findall("{DAV:}response/{DAV:}href") .findall("{DAV:}response/{DAV:}href")
self.assertEquals(len(hrefs), 2, "expected 2 hrefs in response")
self.assertEquals(len(hrefs), 4,
"expected 4 hrefs in response, got %d: %s"
% (len(hrefs), ", ".join([ x.text for x in hrefs ])))
self.assertEquals(hrefs[0].text, anonParentColl, self.assertEquals(hrefs[0].text, anonParentColl,
"the first href is not a 'Calendar' parent coll.") "the first href is not a 'Calendar' parent coll.")
anonResource = '%stest-dav-acl/' % anonParentColl anonResource = '%stest-dav-acl/' % anonParentColl
self.assertEquals(hrefs[1].text, anonResource, resourceHrefs = { anonResource: False,
"expected href '%s' instead of '%s'."\ "%s.xml" % anonResource[:-1]: False,
% (anonResource, hrefs[1].text)) "%s.ics" % anonResource[:-1]: False }
for href in hrefs[1:]:
self.assertTrue(resourceHrefs.has_key(href.text),
"received unexpected href: %s" % href.text)
self.assertFalse(resourceHrefs[href.text],
"href was returned more than once: %s" % href.text)
resourceHrefs[href.text] = True
self.subscriber_client.execute(propfind) self.subscriber_client.execute(propfind)
hrefs = propfind.response["document"] \ hrefs = propfind.response["document"] \

80
Tests/Stress/webdavsync.py Executable file
View file

@ -0,0 +1,80 @@
#!/usr/bin/python
from config import hostname, port
import webdavlib
import random
import time
import threading
base=1127
userscount=100
password=""
batchcount=10
sleeptime=3
durationHeader="sogorequestduration"
#durationHeader="sogo-request-duration"
class StressIteration(threading.Thread):
def __init__(self, username):
threading.Thread.__init__(self)
self.username = username
self.time = 0.0
self.sogoTime = 0.0
def run(self):
client = webdavlib.WebDAVClient(hostname, port,
self.username, password)
resource = "/SOGo/dav/%s/Calendar/personal/" % self.username
startTime = time.time()
query = webdavlib.WebDAVSyncQuery(resource, None,
[ "getetag", "calendar-data" ])
client.execute(query)
if query.response["status"] != 207:
print "*** received unexpected code: %d (%s)" \
% (query.response["status"], resource)
endTime = time.time()
headers = query.response["headers"]
if headers.has_key(durationHeader):
self.sogoTime = float(headers[durationHeader])
self.time = endTime - startTime
# print "%f, %f" % (self.time, self.sogoTime)
class StressTest:
def __init__(self):
self.usernames = [ "invite%d" % (base + x)
for x in xrange(userscount) ]
self.random = random.Random()
def iteration(self):
usernames = self.random.sample(self.usernames, batchcount)
startTime = time.time()
threads = []
for username in usernames:
iteration = StressIteration(username)
iteration.start()
threads.append(iteration)
for thread in threads:
thread.join()
endTime = time.time()
programTime = endTime - startTime
requestsTime = 0.0
sogoTime = 0.0
for thread in threads:
requestsTime = requestsTime + thread.time
sogoTime = sogoTime + thread.sogoTime
print "Iteration time: %f, Total Requests Time: %f, Total SOGo Time: %f" \
% (programTime, requestsTime, sogoTime)
def start(self):
while True:
self.iteration()
time.sleep(sleeptime)
if __name__ == "__main__":
test = StressTest()
test.start()

2
debian/sogo.docs vendored
View file

@ -1,4 +1,4 @@
NEWS NEWS
README README
TODO TODO
Scripts/sql-update-1.2.2_to_1.2.3.sh Scripts/sql-update-1.2.2_to_1.3.0.sh

View file

@ -188,7 +188,7 @@ rm -fr ${RPM_BUILD_ROOT}
%config %{_sysconfdir}/httpd/conf.d/SOGo.conf %config %{_sysconfdir}/httpd/conf.d/SOGo.conf
%config %{_sysconfdir}/sysconfig/sogo %config %{_sysconfdir}/sysconfig/sogo
%doc ChangeLog README NEWS Scripts/sql-update-20070724.sh Scripts/sql-update-20070822.sh Scripts/sql-update-20080303.sh Scripts/sql-update-101_to_102.sh Scripts/sql-update-1.2.2_to_1.2.3.sh %doc ChangeLog README NEWS Scripts/sql-update-20070724.sh Scripts/sql-update-20070822.sh Scripts/sql-update-20080303.sh Scripts/sql-update-101_to_102.sh Scripts/sql-update-1.2.2_to_1.3.0.sh
%files -n sogo-tool %files -n sogo-tool
%{prefix}/Tools/Admin/sogo-tool %{prefix}/Tools/Admin/sogo-tool