merge of '137a22f54c5009171918bdd3a3e136138e9f9a67'

and 'c896a0d9f1b127ab4c1d9c4ba270727464984c2b'

Monotone-Parent: 137a22f54c5009171918bdd3a3e136138e9f9a67
Monotone-Parent: c896a0d9f1b127ab4c1d9c4ba270727464984c2b
Monotone-Revision: 8e95e810adc0d63b540e7ad1bde7d23e688bd3b1

Monotone-Author: flachapelle@inverse.ca
Monotone-Date: 2010-06-02T04:35:59
Monotone-Branch: ca.inverse.sogo
This commit is contained in:
Francis Lachapelle
2010-06-02 04:35:59 +00:00
36 changed files with 906 additions and 499 deletions
+127
View File
@@ -1,9 +1,136 @@
2010-06-02 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* Tests/Integration/test-davacl.py (DAVPublicAccessTest): new test
class that implements tests strictly related to accessing the
"/public" prefix.
(DAVCalendarPublicAclTest.setUp): new test class that tests the
behaviour of calendar resources with regards to the super user,
a non-owner user, the "anonymous" user and the default user
(for default roles).
* Tests/Integration/utilities.py (TestACLUtility.setupRights): we
excape the value for the "user" attribute with
xml.sax.saxutils.escape.
* Tests/Integration/webdavlib.py (HTTPPUT.__init__): added the
"content_type" and "exclusive" optional parameters. The latter
implies the use of the "if-none-match" header.
(WebDAVPUT): removed useless operation class
* SoObjects/SOGo/WORequest+SOGo.m (-handledByDefaultHandler):
fixed a bug where the -requestHandlerKey method would be invoked
on the "request" (NGHttpRequest) ivar rather than on self.
* SoObjects/SOGo/SOGoUserFolder.m (_subFoldersFromFolder:):
thanks to the change below, the ACL checking code is no longer
needed here, where we can now concentrate on returning subfolders
metadata.
* SoObjects/SOGo/SOGoParentFolder.m
(_fetchPersonalFolders:withChannel:): if the active user is not
the owner of the current parent folder, subfolders are returned
only when he/she has permissions set on them even for the
"personal" subfolder.
* SoObjects/SOGo/SOGoGCSFolder.m (-aclsForUser:forObjectAtPath:):
extracted db code into a new "_realAclsForUser:forObjectAtPath:"
private method. When the "None" special role is returned, we
remove it from the list. Finally, we don't allow fetching default
roles when the specified uid is "anonymous".
* SoObjects/Contacts/SOGoContactFolders.m (-appendSystemSources):
we now add public sources to the list of folders if and only if
the active user is the owner of the current parent folder.
* SoObjects/SOGo/SOGoWebAuthenticator.m (-userInContext): if the
returned user has the login "anonymous", we return a corresponding
instance of SOGoUser to make sure methods are never invoked on
SoUser instances.
* SoObjects/SOGo/SOGoUser.m (-rolesForObject:inContext:): now make
use of the new "isInPublicZone" method of SOGoObject to give the
"PublicUser" role to the unauthenticated user.
* SoObjects/SOGo/SOGoObject.m (-isInPublicZone): same as below,
but we cache the result in a new "isInPublicZone" ivar.
* Main/SOGo.m (-lookupName:inContext:acquire): when the "public"
key is requested in DAV mode, we now instantiate a
"SOGoPublicBaseFolder", if the "SOGoEnablePublicAccess" user
default is set.
(-isInPublicZone): new method that always returns "NO" and is
meant to be called from child objects.
* SoObjects/SOGo/SOGoPublicBaseFolder.m: new class module that
enables anonymous access to resources. Instantiated from the
"SOGo" object. Implements a method "-isInPublicZone" that always
returns "YES".
2010-06-02 Francis Lachapelle <flachapelle@inverse.ca>
* UI/Scheduler/UIxCalListingActions.m (_userStateInEvent): fixed
array indexes that would cause the user participation state to no
longer be returned.
2010-06-01 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* SoObjects/SOGo/WORequest+SOGo.m (-handledByDefaultHandler): test
the request handler key instead of invoking isSoWebDAVRequest,
which performs additional and permission tests.
* Tests/Integration/test-davacl.py
(DAVCalendarAclTest._testRights): reenabled test code even when no
classification rights are available.
* SoObjects/SOGo/SOGoSystemDefaults.m (-enablePublicAccess): new
accessor for the SOGoEnablePublicAccess boolean user default.
* SoObjects/SOGo/SOGoUserManager.m
(-contactInfosForUserWithUIDorEmail:): return a fixed dictionary
of information for uid = "anonymous".
* Tests/Integration/test-*.py: take the API changes in
webdavlib.py into accounts with regards to XPath queries.
* SoObjects/Appointments/SOGoAppointmentFolder.m
(-_privacySqlString): renamed to the new standardized
"aclSQLListingFilter". Again, a difference is now made between
empty and nil return strings, which we now apply here too.
(-bareFetchFields:from:to:title:component:additionalFilters:,
(-fetchFields:from:to:title:component:additionalFilters:includeProtectedInformation:):
make use of the new semantics to the above method.
* SoObjects/SOGo/SOGoGCSFolder.m (-aclSQLListingFilter): new
overridable method designed to return a filter to SQL queries that
avoids requesting records that are not meant to be visible to the
active user. Replaces and extends -[SOGoAppointmentFolder
_privacySqlString] with the additional convention that returning a
nil string will prevent any query at all.
(-toOneRelationshipKeys): new overriden method (see below). Make
use of the new aclSQLListingFilter method to retrieve records so
that unaccessible records are not even listed.
* SoObjects/SOGo/SOGoFolder.m (-fetchContentObjectNames): removed
this useless alias to "toOneRelationshipKeys".
* Tests/Integration/all.py: disable multilanguage tests by
default, reverting the "disbale-languages" cmd-line parameter to
"enable-languages".
* Tests/Integration/webdavlib.py (WebDAVClient.__init__): made
"username" and "password" parameters optional to enable anonymous
connections. Also, M2Crypto.httpslib is imported explicitly only
when an SSL connection is requested. This renders this library
optional.
(WebDAVQuery.set_response): we now make use of the xml.etree API
instead of the one from xml.dom.
(xpath_evaluate): the above change makes this method obsolete
since XPath queries can now be performed directly on returned
elements.
* SoObjects/SOGo/SOGoCache.m (-setValue:forKey:expire:): display
the memcached error string when an error occurs.
2010-05-28 Francis Lachapelle <flachapelle@inverse.ca>
* UI/MailerUI/UIxMailMainFrame.m (-saveColumnsStateAction): new
+15 -6
View File
@@ -49,6 +49,7 @@
#import <SOGo/SOGoCache.h>
#import <SOGo/SOGoDAVAuthenticator.h>
#import <SOGo/SOGoPermissions.h>
#import <SOGo/SOGoPublicBaseFolder.h>
#import <SOGo/SOGoProductLoader.h>
#import <SOGo/SOGoProxyAuthenticator.h>
#import <SOGo/SOGoUserFolder.h>
@@ -305,18 +306,21 @@ static BOOL debugLeaks;
id obj;
WORequest *request;
BOOL isDAVRequest;
SOGoSystemDefaults *sd;
/* put locale info into the context in case it's not there */
[self _setupLocaleInContext:_ctx];
sd = [SOGoSystemDefaults sharedSystemDefaults];
request = [_ctx request];
isDAVRequest = [request isSoWebDAVRequest];
if (isDAVRequest
|| [[SOGoSystemDefaults sharedSystemDefaults] isWebAccessEnabled])
isDAVRequest = [[request requestHandlerKey] isEqualToString:@"dav"];
if (isDAVRequest || [sd isWebAccessEnabled])
{
if (isDAVRequest)
{
if ([[request method] isEqualToString: @"REPORT"])
if ([_key isEqualToString: @"public"] && [sd enablePublicAccess])
obj = [SOGoPublicBaseFolder objectWithName: @"public" inContainer: self];
else if ([[request method] isEqualToString: @"REPORT"])
obj = [self davReportInvocationForKey: _key];
else
obj = nil;
@@ -326,6 +330,7 @@ static BOOL debugLeaks;
/* first check attributes directly bound to the application */
obj = [super lookupName:_key inContext:_ctx acquire:_flag];
}
if (!obj)
{
/*
@@ -335,7 +340,6 @@ static BOOL debugLeaks;
Addition: we also get queries for various other methods, like
"GET" if no method was provided in the query path.
*/
if ([_key length] > 0 && ![_key isEqualToString:@"favicon.ico"])
obj = [self lookupUser: _key inContext: _ctx];
}
@@ -346,6 +350,11 @@ static BOOL debugLeaks;
return obj;
}
- (BOOL) isInPublicZone
{
return NO;
}
/* WebDAV */
- (NSString *) davDisplayName
@@ -422,7 +431,7 @@ static BOOL debugLeaks;
[self logWithFormat: @"request took %f seconds to execute",
timeDelta];
[resp setHeader: [NSString stringWithFormat: @"%f", timeDelta]
forKey: @"SOGoRequestDuration"];
forKey: @"SOGo-Request-Duration"];
}
if (![self isTerminating])
+118 -132
View File
@@ -444,70 +444,41 @@ static NSNumber *sharedYes = nil;
end, start];
}
- (NSString *) _privacyClassificationStringsForUID: (NSString *) uid
- (NSString *) aclSQLListingFilter
{
NSMutableString *classificationString;
NSString *currentRole;
unsigned int counter;
iCalAccessClass classes[] = {iCalAccessPublic, iCalAccessPrivate,
iCalAccessConfidential};
classificationString = [NSMutableString string];
for (counter = 0; counter < 3; counter++)
{
currentRole = [self roleForComponentsWithAccessClass: classes[counter]
forUser: uid];
if ([currentRole length] > 0)
[classificationString appendFormat: @"c_classification = %d or ",
classes[counter]];
}
return classificationString;
}
- (NSString *) _privacySqlString
{
NSString *privacySqlString, *login;
NSString *filter;
NSMutableArray *grantedClasses, *deniedClasses;
NSNumber *classNumber;
unsigned int grantedCount;
iCalAccessClass currentClass;
login = [[context activeUser] login];
if ([login isEqualToString: @"freebusy"])
privacySqlString = @"c_isopaque = 1";
else
[self initializeQuickTablesAclsInContext: context];
grantedClasses = [NSMutableArray arrayWithCapacity: 3];
deniedClasses = [NSMutableArray arrayWithCapacity: 3];
for (currentClass = 0;
currentClass < iCalAccessClassCount; currentClass++)
{
[self initializeQuickTablesAclsInContext: context];
grantedClasses = [NSMutableArray arrayWithCapacity: 3];
deniedClasses = [NSMutableArray arrayWithCapacity: 3];
for (currentClass = 0;
currentClass < iCalAccessClassCount; currentClass++)
{
classNumber = [NSNumber numberWithInt: currentClass];
if (userCanAccessObjectsClassifiedAs[currentClass])
[grantedClasses addObject: classNumber];
else
[deniedClasses addObject: classNumber];
}
grantedCount = [grantedClasses count];
if (grantedCount == 3)
privacySqlString = @"";
else if (grantedCount == 2)
privacySqlString
= [NSString stringWithFormat: @"c_classification != %@",
[deniedClasses objectAtIndex: 0]];
else if (grantedCount == 1)
privacySqlString
= [NSString stringWithFormat: @"c_classification = %@",
[grantedClasses objectAtIndex: 0]];
classNumber = [NSNumber numberWithInt: currentClass];
if (userCanAccessObjectsClassifiedAs[currentClass])
[grantedClasses addObject: classNumber];
else
/* We prevent any event/task from being listed. There must be a better
way... */
privacySqlString = @"c_classification = 255";
[deniedClasses addObject: classNumber];
}
grantedCount = [grantedClasses count];
if (grantedCount == 3)
filter = @"";
else if (grantedCount == 2)
filter
= [NSString stringWithFormat: @"c_classification != %@",
[deniedClasses objectAtIndex: 0]];
else if (grantedCount == 1)
filter
= [NSString stringWithFormat: @"c_classification = %@",
[grantedClasses objectAtIndex: 0]];
else
filter = nil;
return privacySqlString;
return filter;
}
- (NSArray *) bareFetchFields: (NSArray *) fields
@@ -520,8 +491,9 @@ static NSNumber *sharedYes = nil;
EOQualifier *qualifier;
GCSFolder *folder;
NSString *sql, *dateSqlString, *titleSqlString, *componentSqlString,
*privacySqlString;
*privacySQLString;
NSMutableString *filterSqlString;
NSArray *records;
folder = [self ocsFolder];
if (startDate && endDate)
@@ -544,25 +516,32 @@ static NSNumber *sharedYes = nil;
if ([filters length])
[filterSqlString appendFormat: @"AND (%@)", filters];
privacySqlString = [self _privacySqlString];
if ([privacySqlString length])
[filterSqlString appendFormat: @"AND (%@)", privacySqlString];
privacySQLString = [self aclSQLListingFilter];
if (privacySQLString)
{
if ([privacySQLString length])
[filterSqlString appendFormat: @"AND (%@)", privacySQLString];
/* prepare mandatory fields */
/* prepare mandatory fields */
sql = [NSString stringWithFormat: @"%@%@%@%@",
dateSqlString, titleSqlString, componentSqlString,
filterSqlString];
/* sql is empty when we fetch everything (all parameters are nil) */
if ([sql length] > 0)
sql = [sql substringFromIndex: 4];
sql = [NSString stringWithFormat: @"%@%@%@%@",
dateSqlString, titleSqlString, componentSqlString,
filterSqlString];
/* sql is empty when we fetch everything (all parameters are nil) */
if ([sql length] > 0)
sql = [sql substringFromIndex: 4];
else
sql = nil;
/* fetch non-recurrent apts first */
qualifier = [EOQualifier qualifierWithQualifierFormat: sql];
records = [folder fetchFields: fields matchingQualifier: qualifier];
}
else
sql = nil;
/* fetch non-recurrent apts first */
qualifier = [EOQualifier qualifierWithQualifierFormat: sql];
records = [NSArray array];
return [folder fetchFields: fields matchingQualifier: qualifier];
return records;
}
- (BOOL) _checkIfWeCanRememberRecords: (NSArray *) fields
@@ -965,7 +944,7 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
NSMutableArray *fields, *ma;
NSArray *records;
NSMutableString *baseWhere;
NSString *where, *dateSqlString, *privacySqlString, *currentLogin;
NSString *where, *dateSqlString, *privacySQLString, *currentLogin;
NSCalendarDate *endDate;
NGCalendarDateRange *r;
BOOL rememberRecords, canCycle;
@@ -1005,76 +984,83 @@ firstInstanceCalendarDateRange: (NGCalendarDateRange *) fir
dateSqlString = @"";
}
privacySqlString = [self _privacySqlString];
if ([privacySqlString length])
[baseWhere appendFormat: @"AND %@", privacySqlString];
if ([title length])
[baseWhere appendFormat: @"AND c_title isCaseInsensitiveLike: '%%%@%%'",
[title stringByReplacingString: @"'" withString: @"\\'\\'"]];
if ([filters length])
[baseWhere appendFormat: @"AND (%@)", filters];
/* prepare mandatory fields */
fields = [NSMutableArray arrayWithArray: _fields];
[fields addObjectUniquely: @"c_name"];
[fields addObjectUniquely: @"c_uid"];
[fields addObjectUniquely: @"c_startdate"];
[fields addObjectUniquely: @"c_enddate"];
[fields addObjectUniquely: @"c_isallday"];
if (canCycle)
where = [NSString stringWithFormat: @"%@ %@ AND c_iscycle = 0",
baseWhere, dateSqlString];
else
where = baseWhere;
/* fetch non-recurrent apts first */
qualifier = [EOQualifier qualifierWithQualifierFormat:
[where substringFromIndex: 4]];
records = [folder fetchFields: fields matchingQualifier: qualifier];
if (records)
privacySQLString = [self aclSQLListingFilter];
if (privacySQLString)
{
if (r)
records = [self fixupRecords: records];
ma = [NSMutableArray arrayWithArray: records];
}
else
ma = nil;
if ([privacySQLString length])
[baseWhere appendFormat: @"AND %@", privacySQLString];
/* fetch recurrent apts now. we do NOT consider events with no cycle end. */
// || _endDate || filters)
if (canCycle && _endDate)
{
where = [NSString stringWithFormat: @"%@ AND c_iscycle = 1", baseWhere];
qualifier = [EOQualifier qualifierWithQualifierFormat: [where substringFromIndex: 4]];
if ([title length])
[baseWhere
appendFormat: @"AND c_title isCaseInsensitiveLike: '%%%@%%'",
[title stringByReplacingString: @"'" withString: @"\\'\\'"]];
if ([filters length])
[baseWhere appendFormat: @"AND (%@)", filters];
/* prepare mandatory fields */
fields = [NSMutableArray arrayWithArray: _fields];
[fields addObjectUniquely: @"c_name"];
[fields addObjectUniquely: @"c_uid"];
[fields addObjectUniquely: @"c_startdate"];
[fields addObjectUniquely: @"c_enddate"];
[fields addObjectUniquely: @"c_isallday"];
if (canCycle)
where = [NSString stringWithFormat: @"%@ %@ AND c_iscycle = 0",
baseWhere, dateSqlString];
else
where = baseWhere;
/* fetch non-recurrent apts first */
qualifier = [EOQualifier qualifierWithQualifierFormat:
[where substringFromIndex: 4]];
records = [folder fetchFields: fields matchingQualifier: qualifier];
if (records)
{
if (r)
records = [self _flattenCycleRecords: records fetchRange: r];
if (ma)
[ma addObjectsFromArray: records];
else
ma = [NSMutableArray arrayWithArray: records];
records = [self fixupRecords: records];
ma = [NSMutableArray arrayWithArray: records];
}
}
if (!ma)
{
[self errorWithFormat: @"(%s): fetch failed!", __PRETTY_FUNCTION__];
return nil;
}
else
ma = nil;
currentLogin = [[context activeUser] login];
if (![currentLogin isEqualToString: owner] && !_includeProtectedInformation)
[self _fixupProtectedInformation: [ma objectEnumerator]
inFields: _fields
forUser: currentLogin];
/* fetch recurrent apts now. we do NOT consider events with no cycle
end. */
if (canCycle && _endDate)
{
where = [NSString stringWithFormat: @"%@ AND c_iscycle = 1", baseWhere];
qualifier = [EOQualifier qualifierWithQualifierFormat: [where substringFromIndex: 4]];
records = [folder fetchFields: fields matchingQualifier: qualifier];
if (records)
{
if (r)
records = [self _flattenCycleRecords: records fetchRange: r];
if (ma)
[ma addObjectsFromArray: records];
else
ma = [NSMutableArray arrayWithArray: records];
}
}
if (!ma)
{
[self errorWithFormat: @"(%s): fetch failed!", __PRETTY_FUNCTION__];
return nil;
}
if (rememberRecords)
[self _rememberRecords: ma];
currentLogin = [[context activeUser] login];
if (![currentLogin isEqualToString: owner]
&& !_includeProtectedInformation)
[self _fixupProtectedInformation: [ma objectEnumerator]
inFields: _fields
forUser: currentLogin];
if (rememberRecords)
[self _rememberRecords: ma];
}
else
ma = [NSMutableArray array];
return ma;
}
+2 -2
View File
@@ -100,8 +100,8 @@
superclass = "SOGoContentObject";
protectedBy = "Access Contents Information";
defaultRoles = {
"Access Contents Information" = ( "Owner", "Authenticated" );
"WebDAV Access" = ( "Owner", "Authenticated" );
"Access Contents Information" = ( "Owner", "Authenticated", "PublicUser" );
"WebDAV Access" = ( "Owner", "Authenticated", "PublicUser" );
};
};
};
+17 -10
View File
@@ -58,18 +58,25 @@
NSEnumerator *sourceIDs;
NSString *currentSourceID, *srcDisplayName, *domain;
SOGoContactSourceFolder *currentFolder;
SOGoUser *currentUser;
domain = [[context activeUser] domain];
um = [SOGoUserManager sharedUserManager];
sourceIDs = [[um addressBookSourceIDsInDomain: domain] objectEnumerator];
while ((currentSourceID = [sourceIDs nextObject]))
currentUser = [context activeUser];
if (activeUserIsOwner
|| [[currentUser login]
isEqualToString: [self ownerInContext: context]])
{
srcDisplayName = [um displayNameForSourceWithID: currentSourceID];
currentFolder = [SOGoContactSourceFolder folderWithName: currentSourceID
andDisplayName: srcDisplayName
inContainer: self];
[currentFolder setSource: [um sourceWithID: currentSourceID]];
[subFolders setObject: currentFolder forKey: currentSourceID];
domain = [currentUser domain];
um = [SOGoUserManager sharedUserManager];
sourceIDs = [[um addressBookSourceIDsInDomain: domain] objectEnumerator];
while ((currentSourceID = [sourceIDs nextObject]))
{
srcDisplayName = [um displayNameForSourceWithID: currentSourceID];
currentFolder = [SOGoContactSourceFolder folderWithName: currentSourceID
andDisplayName: srcDisplayName
inContainer: self];
[currentFolder setSource: [um sourceWithID: currentSourceID]];
[subFolders setObject: currentFolder forKey: currentSourceID];
}
}
return nil;
+1
View File
@@ -86,6 +86,7 @@ SOGo_OBJC_FILES = \
SOGoFolder.m \
SOGoGCSFolder.m \
SOGoParentFolder.m \
SOGoPublicBaseFolder.m \
SOGoUserFolder.m \
\
SOGoDefaultsSource.m \
+2 -2
View File
@@ -233,8 +233,8 @@ static memcached_st *handle = NULL;
expiration, 0);
if (error != MEMCACHED_SUCCESS)
[self logWithFormat:
@"memcached error: unable to cache values for key '%@'",
key];
@"an error occurred when caching value for key '%@':"
@" \"%s\"", key, memcached_strerror(handle, error)];
//else
//[self logWithFormat: @"memcached: cached values (%s) with subtype %@
//for user %@", value, theType, theLogin];
-1
View File
@@ -40,7 +40,6 @@
- (NSString *) realNameInContainer;
- (NSString *) folderType;
- (NSArray *) fetchContentObjectNames;
- (BOOL) isValidContentName: (NSString *) name;
+1 -7
View File
@@ -144,15 +144,9 @@
return nil;
}
#warning we should remove this method
- (NSArray *) toOneRelationshipKeys
{
return [self fetchContentObjectNames];
}
- (NSArray *) fetchContentObjectNames
{
return [NSArray array];
return nil;
}
- (NSArray *) toManyRelationshipKeys
-2
View File
@@ -85,8 +85,6 @@
- (id) createChildComponentWithName: (NSString *) newName
andContent: (NSString *) newContent;
- (NSArray *) fetchContentObjectNames;
/* folder type */
- (BOOL) folderIsMandatory;
+76 -22
View File
@@ -550,24 +550,63 @@ static NSArray *childRecordFields = nil;
[self _subscriberRenameTo: newName];
}
- (NSArray *) fetchContentObjectNames
- (NSString *) aclSQLListingFilter
{
NSString *filter, *login;
NSArray *roles;
login = [[context activeUser] login];
if (activeUserIsOwner
|| [[self ownerInContext: nil] isEqualToString: login])
filter = @"";
else
{
roles = [self aclsForUser: login];
if ([roles containsObject: SOGoRole_ObjectViewer]
|| [roles containsObject: SOGoRole_ObjectEditor])
filter = @"";
else
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;
}
- (NSArray *) toOneRelationshipKeys
{
NSArray *records, *names;
records = [[self ocsFolder] fetchFields: childRecordFields
matchingQualifier:nil];
if (![records isNotNull])
{
[self errorWithFormat: @"(%s): fetch failed!", __PRETTY_FUNCTION__];
return nil;
}
if ([records isKindOfClass: [NSException class]])
return records;
NSString *sqlFilter;
EOQualifier *qualifier;
[childRecords release];
names = [records objectsForKey: @"c_name" notFoundMarker: nil];
childRecords = [[NSMutableDictionary alloc] initWithObjects: records
forKeys: names];
sqlFilter = [self aclSQLListingFilter];
if (sqlFilter)
{
if ([sqlFilter length] > 0)
qualifier = [EOQualifier qualifierWithQualifierFormat: sqlFilter];
else
qualifier = nil;
records = [[self ocsFolder] fetchFields: childRecordFields
matchingQualifier: qualifier];
if (![records isNotNull])
{
[self errorWithFormat: @"(%s): fetch failed!", __PRETTY_FUNCTION__];
return nil;
}
if ([records isKindOfClass: [NSException class]])
return records;
names = [records objectsForKey: @"c_name" notFoundMarker: nil];
[childRecords release];
childRecords = [[NSMutableDictionary alloc] initWithObjects: records
forKeys: names];
}
else
names = [NSArray array];
return names;
}
@@ -1345,13 +1384,12 @@ static NSArray *childRecordFields = nil;
[aclsForObject removeObjectForKey: uid];
}
- (NSArray *) aclsForUser: (NSString *) uid
forObjectAtPath: (NSArray *) objectPathArray
- (NSArray *) _realAclsForUser: (NSString *) uid
forObjectAtPath: (NSArray *) objectPathArray
{
NSArray *acls;
NSString *objectPath, *module;
NSString *objectPath;
NSDictionary *aclsForObject;
SOGoDomainDefaults *dd;
objectPath = [objectPathArray componentsJoinedByString: @"/"];
aclsForObject = [aclCache objectForKey: objectPath];
@@ -1362,13 +1400,26 @@ static NSArray *childRecordFields = nil;
if (!acls)
{
acls = [self _fetchAclsForUser: uid forObjectAtPath: objectPath];
if (!acls)
if (!acls
|| ([acls count] == 1 && [acls containsObject: SOGoRole_None]))
acls = [NSArray array];
[self _cacheRoles: acls forUser: uid forObjectAtPath: objectPath];
}
if (!([acls count] || [uid isEqualToString: defaultUserID]))
acls = [self aclsForUser: defaultUserID forObjectAtPath: objectPathArray];
return acls;
}
- (NSArray *) aclsForUser: (NSString *) uid
forObjectAtPath: (NSArray *) objectPathArray
{
NSArray *acls;
NSString *module;
SOGoDomainDefaults *dd;
acls = [self _realAclsForUser: uid forObjectAtPath: objectPathArray];
if (!([acls count] || [uid isEqualToString: @"anonymous"]))
acls = [self _realAclsForUser: defaultUserID
forObjectAtPath: objectPathArray];
// If we still don't have ACLs defined for this particular resource,
// let's go get the domain defaults, if any.
@@ -1472,6 +1523,9 @@ static NSArray *childRecordFields = nil;
forObjectAtPath: objectPathArray];
newRoles = [NSMutableArray arrayWithArray: roles];
[newRoles removeObject: SoRole_Authenticated];
[newRoles removeObject: SoRole_Anonymous];
[newRoles removeObject: SOGoRole_PublicUser];
[newRoles removeObject: SOGoRole_AuthorizedSubscriber];
[newRoles removeObject: SOGoRole_None];
objectPath = [objectPathArray componentsJoinedByString: @"/"];
+3
View File
@@ -68,6 +68,7 @@
SOGoWebDAVAclManager *webdavAclManager;
id container;
BOOL activeUserIsOwner;
BOOL isInPublicZone;
}
+ (NSString *) globallyUniqueObjectId;
@@ -79,6 +80,8 @@
+ (SOGoWebDAVAclManager *) webdavAclManager;
- (BOOL) isInPublicZone;
/* accessors */
- (NSString *) nameInContainer;
+9
View File
@@ -166,6 +166,7 @@
owner = nil;
webdavAclManager = [[self class] webdavAclManager];
activeUserIsOwner = NO;
isInPublicZone = NO;
}
return self;
@@ -228,6 +229,14 @@
return owner;
}
- (BOOL) isInPublicZone
{
if (!isInPublicZone)
isInPublicZone = [container isInPublicZone];
return isInPublicZone;
}
/* hierarchy */
- (NSArray *) fetchSubfolders
+19 -12
View File
@@ -175,10 +175,12 @@ static SoSecurityManager *sm = nil;
{
NSArray *attrs;
NSDictionary *row;
BOOL hasPersonal;
BOOL hasPersonal, ignoreRights;
SOGoGCSFolder *folder;
NSString *key;
NSString *key, *login;
NSException *error;
SOGoUser *currentUser;
SoSecurityManager *securityManager;
if (!subFolderClass)
subFolderClass = [[self class] subFolderClass];
@@ -187,23 +189,33 @@ static SoSecurityManager *sm = nil;
error = [fc evaluateExpressionX: sql];
if (!error)
{
currentUser = [context activeUser];
login = [currentUser login];
ignoreRights = (activeUserIsOwner || [login isEqualToString: owner]
|| [currentUser isSuperUser]);
if (!ignoreRights)
securityManager = [SoSecurityManager sharedSecurityManager];
attrs = [fc describeResults: NO];
row = [fc fetchAttributes: attrs withZone: NULL];
while (row)
while ((row = [fc fetchAttributes: attrs withZone: NULL]))
{
key = [row objectForKey: @"c_path4"];
if ([key isKindOfClass: [NSString class]])
{
folder = [subFolderClass objectWithName: key inContainer: self];
hasPersonal = (hasPersonal || [key isEqualToString: @"personal"]);
hasPersonal = (hasPersonal
|| [key isEqualToString: @"personal"]);
[folder setOCSPath: [NSString stringWithFormat: @"%@/%@",
OCSPath, key]];
if (ignoreRights
|| ![securityManager validatePermission: SOGoPerm_AccessObject
onObject: folder
inContext: context])
[subFolders setObject: folder forKey: key];
}
row = [fc fetchAttributes: attrs withZone: NULL];
}
if (!hasPersonal)
if (ignoreRights && !hasPersonal)
[self _createPersonalFolder];
}
@@ -382,11 +394,6 @@ static SoSecurityManager *sm = nil;
return error;
}
- (NSArray *) fetchContentObjectNames
{
return nil;
}
- (id) lookupName: (NSString *) name
inContext: (WOContext *) lookupContext
acquire: (BOOL) acquire
+1
View File
@@ -37,6 +37,7 @@ extern NSString *SOGoRole_FolderEraser;
extern NSString *SOGoRole_FolderViewer;
extern NSString *SOGoRole_AuthorizedSubscriber;
extern NSString *SOGoRole_PublicUser;
extern NSString *SOGoRole_None;
extern NSString *SOGoMailRole_SeenKeeper;
+1
View File
@@ -32,6 +32,7 @@ NSString *SOGoRole_FolderCreator = @"FolderCreator";
NSString *SOGoRole_FolderEraser = @"FolderEraser";
NSString *SOGoRole_AuthorizedSubscriber = @"AuthorizedSubscriber";
NSString *SOGoRole_PublicUser = @"PublicUser";
NSString *SOGoRole_None = @"None";
/* Calendar */
+31
View File
@@ -0,0 +1,31 @@
/* SOGoPublicBaseFolder.h - this file is part of SOGo
*
* Copyright (C) 2010 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef SOGOPUBLICBASEFOLDER_H
#define SOGOPUBLICBASEFOLDER_H
#import "SOGoFolder.h"
@interface SOGoPublicBaseFolder : SOGoFolder
@end
#endif /* SOGOPUBLICBASEFOLDER_H */
+50
View File
@@ -0,0 +1,50 @@
/* SOGoPublicBaseFolder.m - this file is part of SOGo
*
* Copyright (C) 2010 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#import <Foundation/NSString.h>
#import "SOGoUser.h"
#import "SOGoPublicBaseFolder.h"
@implementation SOGoPublicBaseFolder
- (id) lookupName: (NSString *) key
inContext: (id) localContext
acquire: (BOOL) acquire
{
id userFolder;
if ([key length] > 0 && [SOGoUser userWithLogin: key roles: nil])
userFolder = [SOGoUserFolder objectWithName: key inContainer: self];
else
userFolder = nil;
return userFolder;
}
- (BOOL) isInPublicZone
{
return YES;
}
@end
+2
View File
@@ -64,6 +64,8 @@
- (NSString *) CASServiceURL;
- (BOOL) enablePublicAccess;
@end
#endif /* SOGOSYSTEMDEFAULTS_H */
+5
View File
@@ -296,4 +296,9 @@ BootstrapNSUserDefaults ()
return [self stringForKey: @"SOGoCASServiceURL"];
}
- (BOOL) enablePublicAccess
{
return [self boolForKey: @"SOGoEnablePublicAccess"];
}
@end
+3
View File
@@ -650,6 +650,9 @@
sogoRoles = [(SOGoObject *) object subscriptionRoles];
if ([sogoRoles firstObjectCommonWithArray: rolesForObject])
[rolesForObject addObject: SOGoRole_AuthorizedSubscriber];
if ([login isEqualToString: @"anonymous"]
&& [(SOGoObject *) object isInPublicZone])
[rolesForObject addObject: SOGoRole_PublicUser];
}
#warning this is a hack to work-around the poor implementation of PROPPATCH in SOPE
+9 -17
View File
@@ -27,7 +27,6 @@
#import <NGObjWeb/NSException+HTTP.h>
#import <NGObjWeb/SoClassSecurityInfo.h>
#import <NGObjWeb/SoSecurityManager.h>
#import <NGObjWeb/WOApplication.h>
#import <NGObjWeb/WOContext+SoObjects.h>
#import <NGObjWeb/WORequest.h>
@@ -148,35 +147,28 @@
NSMutableArray *folders;
NSEnumerator *subfolders;
SOGoFolder *currentFolder;
NSString *folderName, *folderOwner;
NSString *folderName;
Class subfolderClass;
NSMutableDictionary *currentDictionary;
SoSecurityManager *securityManager;
folderOwner = [parentFolder ownerInContext: context];
securityManager = [SoSecurityManager sharedSecurityManager];
folders = [NSMutableArray array];
subfolderClass = [[parentFolder class] subFolderClass];
subfolders = [[parentFolder subFolders] objectEnumerator];
while ((currentFolder = [subfolders nextObject]))
{
if (![securityManager validatePermission: SOGoPerm_AccessObject
onObject: currentFolder inContext: context]
&& [[currentFolder ownerInContext: context]
isEqualToString: folderOwner]
&& [NSStringFromClass([currentFolder class]) compare: @"SOGoWebAppointmentFolder"] != NSOrderedSame)
if ([currentFolder isMemberOfClass: subfolderClass])
{
folderName = [NSString stringWithFormat: @"/%@/%@",
[parentFolder nameInContainer],
[currentFolder nameInContainer]];
currentDictionary
= [NSMutableDictionary dictionaryWithCapacity: 3];
currentDictionary = [NSMutableDictionary dictionaryWithCapacity: 4];
[currentDictionary setObject: [currentFolder displayName]
forKey: @"displayName"];
forKey: @"displayName"];
[currentDictionary setObject: folderName forKey: @"name"];
[currentDictionary setObject: folderOwner forKey: @"owner"];
[currentDictionary setObject: [currentFolder folderType]
forKey: @"type"];
forKey: @"type"];
[folders addObject: currentDictionary];
}
}
@@ -587,7 +579,7 @@
/* WebDAV */
- (NSArray *) fetchContentObjectNames
- (NSArray *) toOneRelationshipKeys
{
SOGoSystemDefaults *sd;
SOGoUser *currentUser;
+25 -3
View File
@@ -593,17 +593,39 @@
forLogin: key];
}
- (NSMutableDictionary *) _contactInfosForAnonymous
{
static NSMutableDictionary *user = nil;
if (!user)
{
user = [[NSMutableDictionary alloc] initWithCapacity: 7];
[user setObject: [NSArray arrayWithObject: @"anonymous"]
forKey: @"emails"];
[user setObject: @"Public User" forKey: @"cn"];
[user setObject: @"anonymous" forKey: @"c_uid"];
[user setObject: @"" forKey: @"c_domain"];
[user setObject: [NSNumber numberWithBool: YES]
forKey: @"CalendarAccess"];
[user setObject: [NSNumber numberWithBool: NO]
forKey: @"MailAccess"];
}
return user;
}
- (NSDictionary *) contactInfosForUserWithUIDorEmail: (NSString *) uid
{
NSMutableDictionary *currentUser, *contactInfos;
NSMutableDictionary *currentUser;
NSString *aUID, *jsonUser;
BOOL newUser;
if ([uid length] > 0)
if ([uid isEqualToString: @"anonymous"])
currentUser = [self _contactInfosForAnonymous];
else if ([uid length] > 0)
{
// Remove the "@" prefix used to identified groups in the ACL tables.
aUID = [uid hasPrefix: @"@"] ? [uid substringFromIndex: 1] : uid;
contactInfos = [NSMutableDictionary dictionary];
jsonUser = [[SOGoCache sharedCache] userAttributesForLogin: aUID];
currentUser = [NSMutableDictionary dictionaryWithJSONString: jsonUser];
if (!([currentUser objectForKey: @"emails"]
+1 -1
View File
@@ -110,7 +110,7 @@
SOGoUser *user;
user = (SOGoUser *) [super userInContext: _ctx];
if (!user)
if (!user || [[user login] isEqualToString: @"anonymous"])
{
if (!anonymous)
anonymous = [[SOGoUser alloc]
+1 -1
View File
@@ -37,7 +37,7 @@
- (BOOL) handledByDefaultHandler
{
#warning this should be changed someday
return ![self isSoWebDAVRequest];
return ![[self requestHandlerKey] isEqualToString:@"dav"];
}
- (NSArray *) _propertiesOfElement: (id <DOMElement>) startElement
+1 -1
View File
@@ -52,5 +52,5 @@ AssertionError: event creation/modification: expected status code '403' (receive
- Always set a doc string on the test methods, especially for complex test
cases.
- When writing tests, be aware that contrary to unit tests, functional tests
- When writing tests, be aware that contrarily to unit tests, functional tests
often imply a logical order between the different steps.
+4 -4
View File
@@ -12,11 +12,11 @@ if __name__ == "__main__":
"Russian", "Spanish", "Swedish", "Welsh"]
# We can disable testing all languages
testLanguages = True
opts, args = getopt.getopt (sys.argv[1:], [], ["disable-languages"])
testLanguages = False
opts, args = getopt.getopt (sys.argv[1:], [], ["enable-languages"])
for o, a in opts:
if o == "--disable-languages":
testLanguages = False
if o == "--enable-languages":
testLanguages = True
for mod in os.listdir("."):
+23 -47
View File
@@ -13,29 +13,9 @@ import vobject
import vobject.base
import vobject.icalendar
import webdavlib
import utilities
import StringIO
def fetchUserInfo(login):
client = webdavlib.WebDAVClient(hostname, port, username, password)
resource = "/SOGo/dav/%s/" % login
propfind = webdavlib.WebDAVPROPFIND(resource,
["displayname",
"{urn:ietf:params:xml:ns:caldav}calendar-user-address-set"],
0)
propfind.xpath_namespace = { "D": "DAV:",
"C": "urn:ietf:params:xml:ns:caldav" }
client.execute(propfind)
assert(propfind.response["status"] == 207)
name_nodes = propfind.xpath_evaluate('/D:multistatus/D:response/D:propstat/D:prop/D:displayname',
None)
email_nodes = propfind.xpath_evaluate('/D:multistatus/D:response/D:propstat/D:prop/C:calendar-user-address-set/D:href',
None)
if len(name_nodes[0].childNodes) > 0:
displayName = name_nodes[0].childNodes[0].nodeValue
else:
displayName = ""
return (displayName, email_nodes[0].childNodes[0].nodeValue)
import xml.etree.ElementTree
class CalDAVPropertiesTest(unittest.TestCase):
def setUp(self):
@@ -58,34 +38,29 @@ class CalDAVPropertiesTest(unittest.TestCase):
["{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp"],
0)
self.client.execute(propfind)
propfind.xpath_namespace = { "D": "DAV:",
"C": "urn:ietf:params:xml:ns:caldav" }
propstats = propfind.xpath_evaluate('/D:multistatus/D:response/D:propstat/D:prop/C:schedule-calendar-transp')
self.assertTrue(len(propstats) > 0,
"schedule-calendar-transp not present in response")
node = propstats[0]
status = propfind.xpath_evaluate('D:status',
node.parentNode.parentNode)[0] \
.childNodes[0].nodeValue[9:12]
response = propfind.response["document"].find('{DAV:}response')
propstat = response.find('{DAV:}propstat')
status = propstat.find('{DAV:}status').text[9:12]
self.assertEquals(status, "200",
"schedule-calendar-transp marked as 'Not Found' in response")
values = node.childNodes
nvalues = len(values)
self.assertEquals(nvalues, 1,
"expected 1 value (%d received)" % nvalues)
transp = propstat.find('{DAV:}prop/{urn:ietf:params:xml:ns:caldav}schedule-calendar-transp')
values = transp.getchildren()
self.assertEquals(len(values), 1, "one and only one element expected")
value = values[0]
self.assertEquals(value.__class__.__name__, "Element",
self.assertTrue(isinstance(value, xml.etree.ElementTree._ElementInterface),
"schedule-calendar-transp must be an instance of" \
" %s, not %s"
% ("Element", value.__class__.__name__))
self.assertEquals(value.namespaceURI, "urn:ietf:params:xml:ns:caldav",
"schedule-calendar-transp must have a value in"\
" namespace '%s', not '%s'"
% ("urn:ietf:params:xml:ns:caldav",
value.namespaceURI))
self.assertTrue(value.tagName == "opaque",
% ("_ElementInterface", transp.__class__.__name__))
ns = value.tag[0:31]
tag = value.tag[31:]
self.assertTrue(ns == "{urn:ietf:params:xml:ns:caldav}",
"schedule-calendar-transp must have a value in"\
" namespace '%s', not '%s'"
% ("urn:ietf:params:xml:ns:caldav", ns))
self.assertTrue(tag == "opaque",
"schedule-calendar-transp must be 'opaque' on new" \
" collections, not '%s'" % value.tagName)
" collections, not '%s'" % tag)
## PROPPATCH
newValueNode = "{urn:ietf:params:xml:ns:caldav}thisvaluedoesnotexist"
@@ -123,9 +98,10 @@ class CalDAVITIPDelegationTest(unittest.TestCase):
def setUp(self):
self.client = webdavlib.WebDAVClient(hostname, port,
username, password)
(self.user_name, self.user_email) = fetchUserInfo(username)
(self.attendee1_name, self.attendee1_email) = fetchUserInfo(attendee1)
(self.attendee1_delegate_name, self.attendee1_delegate_email) = fetchUserInfo(attendee1_delegate)
utility = utilities.TestUtility(self, self.client)
(self.user_name, self.user_email) = utility.fetchUserInfo(username)
(self.attendee1_name, self.attendee1_email) = utility.fetchUserInfo(attendee1)
(self.attendee1_delegate_name, self.attendee1_delegate_email) = utility.fetchUserInfo(attendee1_delegate)
self.user_calendar = "/SOGo/dav/%s/Calendar/personal/" % username
self.attendee1_calendar = "/SOGo/dav/%s/Calendar/personal/" % attendee1
+233 -69
View File
@@ -102,7 +102,8 @@ class DAVCalendarAclTest(DAVAclTest):
def __init__(self, arg):
DAVAclTest.__init__(self, arg)
self.acl_utility = utilities.TestCalendarACLUtility(self.client,
self.acl_utility = utilities.TestCalendarACLUtility(self,
self.client,
self.resource)
def setUp(self):
@@ -195,31 +196,21 @@ class DAVCalendarAclTest(DAVAclTest):
" (received '%d')"
% (filename, exp_status, delete.response["status"]))
def _currentUserPrivilegeSet(self, resource, expectFailure = False):
def _currentUserPrivilegeSet(self, resource, expStatus = 207):
propfind = webdavlib.WebDAVPROPFIND(resource,
["{DAV:}current-user-privilege-set"],
0)
self.subscriber_client.execute(propfind)
if expectFailure:
expStatus = 403
else:
expStatus = 207
self.assertEquals(propfind.response["status"], expStatus,
"unexected status code when reading privileges:"
+ " %s instead of %d"
% (propfind.response["status"], expStatus))
privileges = []
if not expectFailure:
propfind.xpath_namespace = { "D": "DAV:" }
response_nodes = propfind.xpath_evaluate("/D:multistatus/D:response/D:propstat/D:prop/D:current-user-privilege-set/D:privilege")
if expStatus < 300:
response_nodes = propfind.response["document"].findall("{DAV:}response/{DAV:}propstat/{DAV:}prop/{DAV:}current-user-privilege-set/{DAV:}privilege")
for node in response_nodes:
privilegeNode = node.childNodes[0]
tagName = privilegeNode.tagName
indexColon = tagName.find(":")
if indexColon > -1:
tagName = tagName[indexColon+1:]
privileges.append("{%s}%s" % (privilegeNode.namespaceURI, tagName))
privileges.extend([x.tag for x in node.getchildren()])
return privileges
@@ -259,22 +250,24 @@ class DAVCalendarAclTest(DAVAclTest):
'{urn:ietf:params:xml:ns:caldav}schedule-respond-vtodo']
expectedPrivileges.extend(extraPrivileges)
if rights.has_key("d"):
extraPrivileges = ["{DAV:}unbind"]
expectedPrivileges.extend(extraPrivileges)
privileges = self._currentUserPrivilegeSet(self.resource,
len(expectedPrivileges) == 0)
expectedPrivileges.append("{DAV:}unbind")
if len(expectedPrivileges) == 0:
expStatus = 404
else:
expStatus = 207
privileges = self._currentUserPrivilegeSet(self.resource, expStatus)
self._comparePrivilegeSets(expectedPrivileges, privileges)
def _testEventDAVAcl(self, event_class, right):
def _testEventDAVAcl(self, event_class, right, error_code):
icsClass = self.classToICSClass[event_class].lower()
for suffix in [ "event", "task" ]:
url = "%s%s-%s.ics" % (self.resource, icsClass, suffix)
if right is None:
expectFailure = True
expStatus = error_code
expectedPrivileges = None
else:
expectFailure = False
expStatus = 207
expectedPrivileges = ['{DAV:}read-current-user-privilege-set',
'{urn:inverse:params:xml:ns:inverse-dav}view-date-and-time',
'{DAV:}read']
@@ -290,8 +283,8 @@ class DAVCalendarAclTest(DAVAclTest):
'{DAV:}write']
expectedPrivileges.extend(extraPrivileges)
privileges = self._currentUserPrivilegeSet(url, expectFailure)
if not expectFailure:
privileges = self._currentUserPrivilegeSet(url, expStatus)
if expStatus != error_code:
self._comparePrivilegeSets(expectedPrivileges, privileges)
def _testRights(self, rights):
@@ -306,6 +299,8 @@ class DAVCalendarAclTest(DAVAclTest):
def _testCreate(self, rights):
if rights.has_key("c") and rights["c"]:
exp_code = 201
elif len(rights) == 0:
exp_code = 404
else:
exp_code = 403
self._putEvent(self.subscriber_client, "creation-test.ics", "PUBLIC",
@@ -314,6 +309,8 @@ class DAVCalendarAclTest(DAVAclTest):
def _testDelete(self, rights):
if rights.has_key("d") and rights["d"]:
exp_code = 204
elif len(rights) == 0:
exp_code = 404
else:
exp_code = 403
self._deleteEvent(self.subscriber_client, "public-event.ics",
@@ -338,9 +335,13 @@ class DAVCalendarAclTest(DAVAclTest):
event = self._webdavSyncEvent(event_class)
self._checkViewEventRight("webdav-sync", event, event_class, right)
self._testModify(event_class, right)
self._testRespondTo(event_class, right)
self._testEventDAVAcl(event_class, right)
if len(rights) > 0:
error_code = 403
else:
error_code = 404
self._testModify(event_class, right, error_code)
self._testRespondTo(event_class, right, error_code)
self._testEventDAVAcl(event_class, right, error_code)
def _getEvent(self, event_class, is_invitation = False):
icsClass = self.classToICSClass[event_class]
@@ -373,32 +374,27 @@ class DAVCalendarAclTest(DAVAclTest):
return task
def _calendarDataInMultistatus(self, query, filename,
response_tag = "D:response"):
response_tag = "{DAV:}response"):
event = None
query.xpath_namespace = { "D": "DAV:",
"C": "urn:ietf:params:xml:ns:caldav" }
response_nodes = query.xpath_evaluate("/D:multistatus/%s" % response_tag)
# print "\n\n\n%s\n\n" % query.response["body"]
# print "\n\n"
response_nodes = query.response["document"].findall("%s" % response_tag)
for response_node in response_nodes:
href_node = query.xpath_evaluate("D:href", response_node)[0]
href = href_node.childNodes[0].nodeValue
href_node = response_node.find("{DAV:}href")
href = href_node.text
if href.endswith(filename):
propstat_nodes = query.xpath_evaluate("D:propstat", response_node)
for propstat_node in propstat_nodes:
status_node = query.xpath_evaluate("D:status",
propstat_node)[0]
status = status_node.childNodes[0].nodeValue
data_nodes = query.xpath_evaluate("D:prop/C:calendar-data",
propstat_node)
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"):
if (len(data_nodes) > 0
and len(data_nodes[0].childNodes) > 0):
event = data_nodes[0].childNodes[0].nodeValue
else:
if not (status.endswith("404 Resource Not Found")
or status.endswith("404 Not Found")):
self.fail("%s: unexpected status code: '%s'"
% (filename, status))
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'"
% (filename, status))
return event
@@ -411,11 +407,11 @@ class DAVCalendarAclTest(DAVAclTest):
["{urn:ietf:params:xml:ns:caldav}calendar-data"],
1)
self.subscriber_client.execute(propfind)
if propfind.response["status"] != 403:
if propfind.response["status"] != 404:
event = self._calendarDataInMultistatus(propfind, filename)
return event
def _multigetEvent(self, event_class):
event = None
@@ -425,7 +421,7 @@ class DAVCalendarAclTest(DAVAclTest):
["{urn:ietf:params:xml:ns:caldav}calendar-data"],
[ url ])
self.subscriber_client.execute(multiget)
if multiget.response["status"] != 403:
if multiget.response["status"] != 404:
event = self._calendarDataInMultistatus(multiget, url)
return event
@@ -438,9 +434,9 @@ class DAVCalendarAclTest(DAVAclTest):
sync_query = webdavlib.WebDAVSyncQuery(self.resource, None,
["{urn:ietf:params:xml:ns:caldav}calendar-data"])
self.subscriber_client.execute(sync_query)
if sync_query.response["status"] != 403:
if sync_query.response["status"] != 404:
event = self._calendarDataInMultistatus(sync_query, url,
"D:sync-response")
"{DAV:}sync-response")
return event
@@ -497,17 +493,17 @@ class DAVCalendarAclTest(DAVAclTest):
"expected key '%s' not found in secure event"
% key)
def _testModify(self, event_class, right):
def _testModify(self, event_class, right, error_code):
if right == "m" or right == "r":
exp_code = 204
else:
exp_code = 403
exp_code = error_code
icsClass = self.classToICSClass[event_class]
filename = "%s-event.ics" % icsClass.lower()
self._putEvent(self.subscriber_client, filename, icsClass,
exp_code)
def _testRespondTo(self, event_class, right):
def _testRespondTo(self, event_class, right, error_code):
icsClass = self.classToICSClass[event_class]
filename = "invitation-%s-event.ics" % icsClass.lower()
self._putEvent(self.client, filename, icsClass,
@@ -518,7 +514,7 @@ class DAVCalendarAclTest(DAVAclTest):
if right == "m" or right == "r":
exp_code = 204
else:
exp_code = 403
exp_code = error_code
# here we only do 'passive' validation: if a user has a "respond to"
# right, only the attendee entry will me modified. The change of
@@ -660,7 +656,8 @@ END:VCARD""" }
def __init__(self, arg):
DAVAclTest.__init__(self, arg)
self.acl_utility = utilities.TestAddressBookACLUtility(self.client,
self.acl_utility = utilities.TestAddressBookACLUtility(self,
self.client,
self.resource)
def setUp(self):
@@ -685,28 +682,23 @@ END:VCARD""" }
def testCreateDelete(self):
"""'create', 'delete'"""
self._testRights({ "c": True,
"d": True })
self._testRights({ "c": True, "d": True })
def testViewCreate(self):
"""'view' and 'create'"""
self._testRights({ "c": True,
"v": True })
self._testRights({ "c": True, "v": True })
def testViewDelete(self):
"""'view' and 'delete'"""
self._testRights({ "d": True,
"v": True })
self._testRights({ "d": True, "v": True })
def testEditCreate(self):
"""'edit' and 'create'"""
self._testRights({ "c": True,
"e": True })
self._testRights({ "c": True, "e": True })
def testEditDelete(self):
"""'edit' and 'delete'"""
self._testRights({ "d": True,
"e": True })
self._testRights({ "d": True, "e": True })
def _testRights(self, rights):
self.acl_utility.setupRights(subscriber_username, rights)
@@ -775,6 +767,178 @@ END:VCARD""" }
exp_code = 403
self._deleteCard(self.subscriber_client, "old.vcf", exp_code)
class DAVPublicAccessTest(unittest.TestCase):
def __init__(self, arg):
unittest.TestCase.__init__(self, arg)
self.client = webdavlib.WebDAVClient(hostname, port)
self.anon_client = webdavlib.WebDAVClient(hostname, port)
self.dav_utility = utilities.TestUtility(self, self.client)
def testPublicAccess(self):
resource = '/SOGo/so/public'
options = webdavlib.HTTPOPTIONS(resource)
self.anon_client.execute(options)
self.assertEquals(options.response["status"], 404,
"/SOGo/so/public is unexpectedly available")
resource = '/SOGo/public'
options = webdavlib.HTTPOPTIONS(resource)
self.anon_client.execute(options)
self.assertEquals(options.response["status"], 404,
"/SOGo/so/public is unexpectedly available")
resource = '/SOGo/dav/%s' % username
options = webdavlib.HTTPOPTIONS(resource)
self.anon_client.execute(options)
self.assertEquals(options.response["status"], 401,
"Non-public resources should request authentication")
resource = '/SOGo/dav/public'
options = webdavlib.HTTPOPTIONS(resource)
self.anon_client.execute(options)
self.assertNotEquals(options.response["status"], 401,
"Non-public resources must NOT request authentication")
self.assertEquals(options.response["status"], 200,
"/SOGo/dav/public is not available, check user defaults")
class DAVCalendarPublicAclTest(unittest.TestCase):
def setUp(self):
self.createdRsrc = None
self.client = webdavlib.WebDAVClient(hostname, port,
username, password)
self.subscriber_client = webdavlib.WebDAVClient(hostname, port,
subscriber_username,
subscriber_password)
self.anon_client = webdavlib.WebDAVClient(hostname, port)
def tearDown(self):
if self.createdRsrc is not None:
delete = webdavlib.WebDAVDELETE(self.createdRsrc)
self.client.execute(delete)
def testCollectionAccessNormalUser(self):
"""normal user access to (non-)shared resource from su"""
# 1. all rights removed
parentColl = '/SOGo/dav/%s/Calendar/' % username
self.createdRsrc = '%stest-dav-acl/' % parentColl
for rsrc in [ 'personal', 'test-dav-acl' ]:
resource = '%s%s/' % (parentColl, rsrc)
mkcol = webdavlib.WebDAVMKCOL(resource)
self.client.execute(mkcol)
acl_utility = utilities.TestCalendarACLUtility(self,
self.client,
resource)
acl_utility.setupRights("anonymous", {})
acl_utility.setupRights(subscriber_username, {})
acl_utility.setupRights("<default>", {})
propfind = webdavlib.WebDAVPROPFIND(parentColl, [ "displayname" ], 1)
self.subscriber_client.execute(propfind)
hrefs = propfind.response["document"] \
.findall("{DAV:}response/{DAV:}href")
self.assertEquals(len(hrefs), 1,
"expected only one href in response")
self.assertEquals(hrefs[0].text, parentColl,
"the href must be the 'Calendar' parent coll.")
acl_utility = utilities.TestCalendarACLUtility(self,
self.client,
self.createdRsrc)
# 2. creation right added
acl_utility.setupRights(subscriber_username, { "c": True })
self.subscriber_client.execute(propfind)
hrefs = propfind.response["document"] \
.findall("{DAV:}response/{DAV:}href")
self.assertEquals(len(hrefs), 2, "expected two hrefs in response")
self.assertEquals(hrefs[0].text, parentColl,
"the first href is not a 'Calendar' parent coll.")
self.assertEquals(hrefs[1].text, resource,
"the 2nd href is not the accessible coll.")
acl_utility.setupRights(subscriber_username, {})
# 3. creation right added for "default user"
# subscriber_username expected to have access, but not "anonymous"
acl_utility.setupRights("<default>", { "c": True })
self.subscriber_client.execute(propfind)
hrefs = propfind.response["document"] \
.findall("{DAV:}response/{DAV:}href")
self.assertEquals(len(hrefs), 2,
"expected two hrefs in response: %d received" \
% len(hrefs))
self.assertEquals(hrefs[0].text, parentColl,
"the first href is not a 'Calendar' parent coll.")
self.assertEquals(hrefs[1].text, resource,
"the 2nd href is not the accessible coll.")
anonParentColl = '/SOGo/dav/public/%s/Calendar/' % username
anon_propfind = webdavlib.WebDAVPROPFIND(anonParentColl,
[ "displayname" ], 1)
self.anon_client.execute(anon_propfind)
hrefs = anon_propfind.response["document"] \
.findall("{DAV:}response/{DAV:}href")
self.assertEquals(len(hrefs), 1, "expected only 1 href in response")
self.assertEquals(hrefs[0].text, anonParentColl,
"the first href is not a 'Calendar' parent coll.")
acl_utility.setupRights("<default>", {})
# 4. creation right added for "anonymous"
# "anonymous" expected to have access, but not subscriber_username
acl_utility.setupRights("anonymous", { "c": True })
self.anon_client.execute(anon_propfind)
hrefs = anon_propfind.response["document"] \
.findall("{DAV:}response/{DAV:}href")
self.assertEquals(len(hrefs), 2, "expected 2 hrefs in response")
self.assertEquals(hrefs[0].text, anonParentColl,
"the first href is not a 'Calendar' parent coll.")
anonResource = '%stest-dav-acl/' % anonParentColl
self.assertEquals(hrefs[1].text, anonResource,
"expected href '%s' instead of '%s'."\
% (anonResource, hrefs[1].text))
self.subscriber_client.execute(propfind)
hrefs = propfind.response["document"] \
.findall("{DAV:}response/{DAV:}href")
self.assertEquals(len(hrefs), 1, "expected only 1 href in response")
self.assertEquals(hrefs[0].text, parentColl,
"the first href is not a 'Calendar' parent coll.")
def testCollectionAccessSuperUser(self):
# super user accessing (non-)shared res from nu
parentColl = '/SOGo/dav/%s/Calendar/' % subscriber_username
self.createdRsrc = '%stest-dav-acl/' % parentColl
for rsrc in [ 'personal', 'test-dav-acl' ]:
resource = '%s%s/' % (parentColl, rsrc)
mkcol = webdavlib.WebDAVMKCOL(resource)
self.client.execute(mkcol)
acl_utility = utilities.TestCalendarACLUtility(self,
self.subscriber_client,
resource)
acl_utility.setupRights(username, {})
propfind = webdavlib.WebDAVPROPFIND(parentColl, [ "displayname" ], 1)
self.subscriber_client.execute(propfind)
hrefs = [x.text \
for x in propfind.response["document"] \
.findall("{DAV:}response/{DAV:}href")]
self.assertTrue(len(hrefs) > 2,
"expected at least 3 hrefs in response")
self.assertEquals(hrefs[0], parentColl,
"the href must be the 'Calendar' parent coll.")
for rsrc in [ 'personal', 'test-dav-acl' ]:
resource = '%s%s/' % (parentColl, rsrc)
self.assertTrue(hrefs.index(resource) > -1,
"resource '%s' not returned" % resource)
if __name__ == "__main__":
unittest.main()
+11 -14
View File
@@ -16,7 +16,6 @@ class iCalTest(unittest.TestCase):
propfind = webdavlib.WebDAVPROPFIND(resource,
["{DAV:}principal-collection-set"],
0)
propfind.xpath_namespace = { "D": "DAV:" }
client.execute(propfind)
self.assertEquals(propfind.response["status"], 207)
headers = propfind.response["headers"]
@@ -28,7 +27,6 @@ class iCalTest(unittest.TestCase):
["{DAV:}principal-collection-set"],
0)
client.user_agent = "DAVKit/4.0.1 (730); CalendarStore/4.0.1 (973); iCal/4.0.1 (1374); Mac OS X/10.6.2 (10C540)"
propfind.xpath_namespace = { "D": "DAV:" }
client.execute(propfind)
self.assertEquals(propfind.response["status"], 207)
headers = propfind.response["headers"]
@@ -50,7 +48,6 @@ class iCalTest(unittest.TestCase):
proppatch = webdavlib.WebDAVPROPPATCH(resource, props)
client = webdavlib.WebDAVClient(hostname, port, username, password)
client.user_agent = "DAVKit/4.0.1 (730); CalendarStore/4.0.1 (973); iCal/4.0.1 (1374); Mac OS X/10.6.2 (10C540)"
proppatch.xpath_namespace = { "D": "DAV:" }
client.execute(proppatch)
self.assertEquals(proppatch.response["status"], 207,
"failure (%s) setting '%s' permission for '%s' on %s's calendars"
@@ -63,11 +60,10 @@ class iCalTest(unittest.TestCase):
["{DAV:}group-membership"], 0)
client = webdavlib.WebDAVClient(hostname, port, username, password)
client.user_agent = "DAVKit/4.0.1 (730); CalendarStore/4.0.1 (973); iCal/4.0.1 (1374); Mac OS X/10.6.2 (10C540)"
propfind.xpath_namespace = { "D": "DAV:" }
client.execute(propfind)
hrefs = propfind.xpath_evaluate("/D:multistatus/D:response/D:propstat/D:prop/D:group-membership/D:href")
members = [x.childNodes[0].nodeValue for x in hrefs]
hrefs = propfind.response["document"].findall("{DAV:}response/{DAV:}propstat/{DAV:}prop/{DAV:}group-membership/{DAV:}href")
members = [x.text for x in hrefs]
return members
@@ -77,12 +73,11 @@ class iCalTest(unittest.TestCase):
propfind = webdavlib.WebDAVPROPFIND(resource, [prop], 0)
client = webdavlib.WebDAVClient(hostname, port, username, password)
client.user_agent = "DAVKit/4.0.1 (730); CalendarStore/4.0.1 (973); iCal/4.0.1 (1374); Mac OS X/10.6.2 (10C540)"
propfind.xpath_namespace = { "D": "DAV:", "n1": "http://calendarserver.org/ns/" }
client.execute(propfind)
hrefs = propfind.xpath_evaluate("/D:multistatus/D:response/D:propstat/D:prop/n1:calendar-proxy-%s-for/D:href"
hrefs = propfind.response["document"].findall("{DAV:}response/{DAV:}propstat/{DAV:}prop/{http://calendarserver.org/ns/}calendar-proxy-%s-for/{DAV:}href"
% perm)
members = [x.childNodes[0].nodeValue[len("/SOGo/dav/"):-1] for x in hrefs]
members = [x.text[len("/SOGo/dav/"):-1] for x in hrefs]
return members
@@ -123,7 +118,7 @@ class iCalTest(unittest.TestCase):
% (users[1], perm, users[0], proxyFor))
def _testMapping(self, client, perm, resource, rights):
dav_utility = utilities.TestCalendarACLUtility(client, resource)
dav_utility = utilities.TestCalendarACLUtility(self, client, resource)
dav_utility.setupRights(subscriber_username, rights)
membership = self._getMembership(subscriber_username)
@@ -142,7 +137,8 @@ class iCalTest(unittest.TestCase):
client = webdavlib.WebDAVClient(hostname, port, username, password)
client.user_agent = "DAVKit/4.0.1 (730); CalendarStore/4.0.1 (973); iCal/4.0.1 (1374); Mac OS X/10.6.2 (10C540)"
personal_resource = "/SOGo/dav/%s/Calendar/personal/" % username
dav_utility = utilities.TestCalendarACLUtility(client,
dav_utility = utilities.TestCalendarACLUtility(self,
client,
personal_resource)
dav_utility.setupRights(subscriber_username, {})
dav_utility.subscribe([subscriber_username])
@@ -153,7 +149,8 @@ class iCalTest(unittest.TestCase):
client.execute(delete)
mkcol = webdavlib.WebDAVMKCOL(other_resource)
client.execute(mkcol)
dav_utility = utilities.TestCalendarACLUtility(client,
dav_utility = utilities.TestCalendarACLUtility(self,
client,
other_resource)
dav_utility.setupRights(subscriber_username, {})
dav_utility.subscribe([subscriber_username])
@@ -181,7 +178,7 @@ class iCalTest(unittest.TestCase):
## we test the unsubscription
# unsubscribed from personal, subscribed to 'test-calendar-proxy2'
dav_utility = utilities.TestCalendarACLUtility(client,
dav_utility = utilities.TestCalendarACLUtility(self, client,
personal_resource)
dav_utility.unsubscribe([subscriber_username])
membership = self._getMembership(subscriber_username)
@@ -190,7 +187,7 @@ class iCalTest(unittest.TestCase):
"'%s' must have write access to %s's calendars"
% (subscriber_username, username))
# unsubscribed from personal, unsubscribed from 'test-calendar-proxy2'
dav_utility = utilities.TestCalendarACLUtility(client,
dav_utility = utilities.TestCalendarACLUtility(self, client,
other_resource)
dav_utility.unsubscribe([subscriber_username])
membership = self._getMembership(subscriber_username)
+7 -13
View File
@@ -17,10 +17,8 @@ def fetchUserEmail(login):
propfind = webdavlib.WebDAVPROPFIND(resource,
["{urn:ietf:params:xml:ns:caldav}calendar-user-address-set"],
0)
propfind.xpath_namespace = { "D": "DAV:",
"C": "urn:ietf:params:xml:ns:caldav" }
client.execute(propfind)
nodes = propfind.xpath_evaluate('/D:multistatus/D:response/D:propstat/D:prop/C:calendar-user-address-set/D:href',
nodes = propfind.xpath_evaluate('{DAV:}response/{DAV:}propstat/{DAV:}prop/C:calendar-user-address-set/{DAV:}href',
None)
return nodes[0].childNodes[0].nodeValue
@@ -225,7 +223,7 @@ class DAVMailCollectionTest():
self.client.execute(propfind)
key = property.replace("{urn:schemas:httpmail:}", "a:")
key = key.replace("{urn:schemas:mailheader:}", "a:")
tmp = propfind.xpath_evaluate("/D:multistatus/D:response/D:propstat/D:prop")
tmp = propfind.xpath_evaluate("{DAV:}response/{DAV:}propstat/{DAV:}prop")
prop = tmp[0].firstChild;
result = None
@@ -319,11 +317,9 @@ class DAVMailCollectionTest():
self.assertEquals(query.response["status"], 207,
"filter %s:\n\tunexpected status: %d"
% (filter[0], query.response["status"]))
query.xpath_namespace = { "D": "DAV:",
"I": "urn:inverse:params:xml:ns:inverse-dav" }
response_nodes = query.xpath_evaluate("/D:multistatus/D:response")
response_nodes = query.xpath_evaluate("{DAV:}response")
for response_node in response_nodes:
href_node = query.xpath_evaluate("D:href", response_node)[0]
href_node = query.xpath_evaluate("{DAV:}href", response_node)[0]
href = href_node.childNodes[0].nodeValue
received_count = received_count + 1
self.assertTrue(expected_hrefs.has_key(href),
@@ -345,12 +341,10 @@ class DAVMailCollectionTest():
self.assertEquals(query.response["status"], 207,
"sortOrder %s:\n\tunexpected status: %d"
% (sortOrder[0], query.response["status"]))
query.xpath_namespace = { "D": "DAV:",
"I": "urn:inverse:params:xml:ns:inverse-dav" }
response_nodes = query.xpath_evaluate("/D:multistatus/D:response")
response_nodes = query.response["document"].findall("{DAV:}response")
for response_node in response_nodes:
href_node = query.xpath_evaluate("D:href", response_node)[0]
href = href_node.childNodes[0].nodeValue
href_node = response_node.find("{DAV:}href")
href = href_node.text
self.assertEquals(expected_hrefs[received_count], href,
"sortOrder %s:\n\tunexpected href: %s (expecting: %s)"
% (sortOrder[0], href,
+36 -67
View File
@@ -11,7 +11,7 @@ class WebDAVTest(unittest.TestCase):
unittest.TestCase.__init__(self, arg)
self.client = webdavlib.WebDAVClient(hostname, port,
username, password)
self.dav_utility = utilities.TestUtility(self.client)
self.dav_utility = utilities.TestUtility(self, self.client)
def testPrincipalCollectionSet(self):
"""property: 'principal-collection-set' on collection object"""
@@ -19,12 +19,11 @@ class WebDAVTest(unittest.TestCase):
propfind = webdavlib.WebDAVPROPFIND(resource,
["{DAV:}principal-collection-set"],
0)
propfind.xpath_namespace = { "D": "DAV:" }
self.client.execute(propfind)
self.assertEquals(propfind.response["status"], 207)
nodes = propfind.xpath_evaluate('/D:multistatus/D:response/D:propstat/D:prop/D:principal-collection-set/D:href',
None)
responseHref = nodes[0].childNodes[0].nodeValue
nodes = propfind.response["document"] \
.findall('{DAV:}response/{DAV:}propstat/{DAV:}prop/{DAV:}principal-collection-set/{DAV:}href')
responseHref = nodes[0].text
if responseHref[0:4] == "http":
self.assertEquals("http://%s/SOGo/dav/" % hostname, responseHref,
"{DAV:}principal-collection-set returned %s instead of 'http../SOGo/dav/'"
@@ -40,12 +39,11 @@ class WebDAVTest(unittest.TestCase):
propfind = webdavlib.WebDAVPROPFIND(resource,
["{DAV:}principal-collection-set"],
0)
propfind.xpath_namespace = { "D": "DAV:" }
self.client.execute(propfind)
self.assertEquals(propfind.response["status"], 207)
nodes = propfind.xpath_evaluate('/D:multistatus/D:response/D:propstat/D:prop/D:principal-collection-set/D:href',
None)
responseHref = nodes[0].childNodes[0].nodeValue
node = propfind.response["document"] \
.find('{DAV:}response/{DAV:}propstat/{DAV:}prop/{DAV:}principal-collection-set/{DAV:}href')
responseHref = node.text
expectedHref = '/SOGo/dav/'
if responseHref[0:4] == "http":
self.assertEquals("http://%s%s" % (hostname, expectedHref), responseHref,
@@ -61,19 +59,15 @@ class WebDAVTest(unittest.TestCase):
propfind = webdavlib.WebDAVPROPFIND(resource,
["{DAV:}displayname", "{DAV:}resourcetype"],
1)
propfind.xpath_namespace = { "D": "DAV:" }
self.client.execute(propfind)
self.assertEquals(propfind.response["status"], 207)
nodes = propfind.xpath_evaluate('/D:multistatus/D:response',
None)
nodes = propfind.response["document"].findall('{DAV:}response')
for node in nodes:
responseHref = propfind.xpath_evaluate('D:href', node)[0].childNodes[0].nodeValue
responseHref = node.find('{DAV:}href').text
hasSlash = responseHref[-1] == '/'
resourcetypes = \
propfind.xpath_evaluate('D:propstat/D:prop/D:resourcetype',
node)[0].childNodes
isCollection = len(resourcetypes) > 0
resourcetype = node.find('{DAV:}propstat/{DAV:}prop/{DAV:}resourcetype')
isCollection = len(resourcetype.getchildren()) > 0
if isCollection:
self.assertEquals(hasSlash, resourceWithSlash,
"failure with href '%s' while querying '%s'"
@@ -108,14 +102,11 @@ class WebDAVTest(unittest.TestCase):
["displayname"], matches)
self.client.execute(query)
self.assertEquals(query.response["status"], 207)
response = query.xpath_evaluate('/D:multistatus/D:response')[0]
href = query.xpath_evaluate('D:href', response)[0]
self.assertEquals("/SOGo/dav/%s/" % username,
href.childNodes[0].nodeValue)
displayname = query.xpath_evaluate('/D:multistatus/D:response' \
+ '/D:propstat/D:prop' \
+ '/D:displayname')[0]
value = displayname.nodeValue
response = query.response["document"].findall('{DAV:}response')[0]
href = response.find('{DAV:}href').text
self.assertEquals("/SOGo/dav/%s/" % username, href)
displayname = response.find('{DAV:}propstat/{DAV:}prop/{DAV:}displayname')
value = displayname.text
if value is None:
value = ""
self.assertEquals(userInfo[0], value)
@@ -126,60 +117,38 @@ class WebDAVTest(unittest.TestCase):
resource = '/SOGo/dav/%s/' % username
userInfo = self.dav_utility.fetchUserInfo(username)
query_props = {"owner": { "href": resource,
"displayname": userInfo[0]},
"principal-collection-set": { "href": "/SOGo/dav/",
"displayname": "SOGo"}}
query_props = {"{DAV:}owner": { "{DAV:}href": resource,
"{DAV:}displayname": userInfo[0]},
"{DAV:}principal-collection-set": { "{DAV:}href": "/SOGo/dav/",
"{DAV:}displayname": "SOGo"}}
query = webdavlib.WebDAVExpandProperty(resource, query_props.keys(),
["displayname"])
self.client.execute(query)
self.assertEquals(query.response["status"], 207)
topResponse = query.xpath_evaluate('/D:multistatus/D:response')[0]
topHref = query.xpath_evaluate('D:href', topResponse)[0]
self.assertEquals(resource, topHref.childNodes[0].nodeValue)
topResponse = query.response["document"].find('{DAV:}response')
topHref = topResponse.find('{DAV:}href')
self.assertEquals(resource, topHref.text)
for query_prop in query_props.keys():
propResponse = query.xpath_evaluate('D:propstat/D:prop/D:%s'
% query_prop, topResponse)[0]
# <?xml version="1.0" encoding="utf-8"?>
# <D:multistatus xmlns:D="DAV:">
# <D:response>
# <D:href>/SOGo/dav/wsourdeau/</D:href>
# <D:propstat>
# <D:prop>
# <D:owner>
# <D:response>
# <D:href>/SOGo/dav/wsourdeau/</D:href>
# <D:propstat>
# <D:prop>
# <D:displayname>Wolfgang Sourdeau</D:displayname>
# </D:prop>
# <D:status>HTTP/1.1 200 OK</D:status>
# </D:propstat>
# </D:response>
# </D:owner>
propHref = query.xpath_evaluate('D:response/D:href',
propResponse)[0]
self.assertEquals(query_props[query_prop]["href"],
propHref.childNodes[0].nodeValue,
propResponse = topResponse.find('{DAV:}propstat/{DAV:}prop/%s'
% query_prop)
propHref = propResponse.find('{DAV:}response/{DAV:}href')
self.assertEquals(query_props[query_prop]["{DAV:}href"],
propHref.text,
"'%s', href mismatch: exp. '%s', got '%s'"
% (query_prop,
query_props[query_prop]["href"],
propHref.childNodes[0].nodeValue))
propDisplayname = query.xpath_evaluate('D:response/D:propstat/D:prop/D:displayname',
propResponse)[0]
if len(propDisplayname.childNodes) > 0:
displayName = propDisplayname.childNodes[0].nodeValue
else:
query_props[query_prop]["{DAV:}href"],
propHref.text))
propDisplayname = propResponse.find('{DAV:}response/{DAV:}propstat/{DAV:}prop/{DAV:}displayname')
displayName = propDisplayname.text
if displayName is None:
displayName = ""
self.assertEquals(query_props[query_prop]["displayname"],
self.assertEquals(query_props[query_prop]["{DAV:}displayname"],
displayName,
"'%s', displayname mismatch: exp. '%s', got '%s'"
% (query_prop,
query_props[query_prop]["displayname"],
propDisplayname.nodeValue))
query_props[query_prop]["{DAV:}displayname"],
propDisplayname))
if __name__ == "__main__":
unittest.main()
+4 -4
View File
@@ -43,10 +43,10 @@ class WebdavSyncTest(unittest.TestCase):
self.assertEquals(query1.response["status"], 207,
("query1: invalid status code: %d (!= 207)"
% query1.response["status"]))
token_node = query1.xpath_evaluate("/D:multistatus/D:sync-token")[0]
token_node = query1.response["document"].find("{DAV:}sync-token")
# Implicit "assertion": we expect SOGo to return a token node, with a
# non-empty numerical value. Anything else will trigger an exception
token = int(token_node.childNodes[0].nodeValue)
token = int(token_node.text)
self.assertTrue(token > 0)
self.assertTrue(token <= int(query1.start))
@@ -55,8 +55,8 @@ class WebdavSyncTest(unittest.TestCase):
query2 = webdavlib.WebDAVSyncQuery(resource, "1234", [ "getetag" ])
self.client.execute(query2)
self.assertEquals(query2.response["status"], 403)
cond_nodes = query2.xpath_evaluate("/D:error/D:valid-sync-token")
self.assertTrue(len(cond_nodes) > 0,
cond_nodes = query2.response["document"].find("{DAV:}valid-sync-token")
self.assertTrue(cond_nodes is not None,
"expected 'valid-sync-token' condition error")
if __name__ == "__main__":
+24 -22
View File
@@ -2,9 +2,11 @@
import unittest
import webdavlib
import xml.sax.saxutils
class TestUtility(unittest.TestCase):
def __init__(self, client):
class TestUtility():
def __init__(self, test, client, resource = None):
self.test = test
self.client = client
self.userInfo = {}
@@ -15,26 +17,26 @@ class TestUtility(unittest.TestCase):
["displayname",
"{urn:ietf:params:xml:ns:caldav}calendar-user-address-set"],
0)
propfind.xpath_namespace = { "D": "DAV:",
"C": "urn:ietf:params:xml:ns:caldav" }
self.client.execute(propfind)
assert(propfind.response["status"] == 207)
name_nodes = propfind.xpath_evaluate('/D:multistatus/D:response/D:propstat/D:prop/D:displayname',
None)
email_nodes = propfind.xpath_evaluate('/D:multistatus/D:response/D:propstat/D:prop/C:calendar-user-address-set/D:href',
None)
self.test.assertEquals(propfind.response["status"], 207)
common_tree = "{DAV:}response/{DAV:}propstat/{DAV:}prop"
name_nodes = propfind.response["document"] \
.findall('%s/{DAV:}displayname' % common_tree)
email_nodes = propfind.response["document"] \
.findall('%s/{urn:ietf:params:xml:ns:caldav}calendar-user-address-set/{DAV:}href'
% common_tree)
if len(name_nodes[0].childNodes) > 0:
displayName = name_nodes[0].childNodes[0].nodeValue
if len(name_nodes[0].text) > 0:
displayName = name_nodes[0].text
else:
displayName = ""
self.userInfo[login] = (displayName, email_nodes[0].childNodes[0].nodeValue)
self.userInfo[login] = (displayName, email_nodes[0].text)
return self.userInfo[login]
class TestACLUtility(TestUtility):
def __init__(self, client, resource):
TestUtility.__init__(self, client)
def __init__(self, test, client, resource):
TestUtility.__init__(self, test, client, resource)
self.resource = resource
def _subscriptionOperation(self, subscribers, operation):
@@ -48,10 +50,10 @@ class TestACLUtility(TestUtility):
post = webdavlib.HTTPPOST(self.resource, subscribeQuery)
post.content_type = "application/xml; charset=\"utf-8\""
self.client.execute(post)
self.assertEquals(post.response["status"], 204,
"subscribtion failure to '%s' for '%s' (status: %d)"
% (self.resource, "', '".join(subscribers),
post.response["status"]))
self.test.assertEquals(post.response["status"], 204,
"subscribtion failure to '%s' for '%s' (status: %d)"
% (self.resource, "', '".join(subscribers),
post.response["status"]))
def subscribe(self, subscribers=None):
self._subscriptionOperation(subscribers, "subscribe")
@@ -68,16 +70,16 @@ class TestACLUtility(TestUtility):
aclQuery = ("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
+ "<acl-query"
+ " xmlns=\"urn:inverse:params:xml:ns:inverse-dav\">"
+ "<set-roles user=\"%s\">%s</set-roles>" % (username,
+ "<set-roles user=\"%s\">%s</set-roles>" % (xml.sax.saxutils.escape(username),
rights_str)
+ "</acl-query>")
post = webdavlib.HTTPPOST(self.resource, aclQuery)
post.content_type = "application/xml; charset=\"utf-8\""
self.client.execute(post)
self.assertEquals(post.response["status"], 204,
"rights modification: failure to set '%s' (status: %d)"
% (rights_str, post.response["status"]))
self.test.assertEquals(post.response["status"], 204,
"rights modification: failure to set '%s' (status: %d)"
% (rights_str, post.response["status"]))
# Calendar:
# rights:
+29 -34
View File
@@ -21,12 +21,11 @@
import cStringIO
import httplib
import M2Crypto.httpslib
import re
import time
import xml.dom.expatbuilder
import xml.etree.ElementTree
import xml.sax.saxutils
import xml.dom.ext.reader.Sax2
import xml.xpath
import sys
xmlns_dav = "DAV:"
@@ -66,19 +65,27 @@ class HTTPUnparsedURL:
class WebDAVClient:
user_agent = "Mozilla/5.0"
def __init__(self, hostname, port, username, password, forcessl = False):
def __init__(self, hostname, port, username = None, password = None,
forcessl = False):
if int(port) == 443 or forcessl:
import M2Crypto.httpslib
self.conn = M2Crypto.httpslib.HTTPSConnection(hostname, int(port),
True)
else:
self.conn = httplib.HTTPConnection(hostname, port, True)
self.simpleauth_hash = (("%s:%s" % (username, password))
.encode('base64')[:-1])
if username is not None:
if password is None:
password = ""
self.simpleauth_hash = (("%s:%s" % (username, password))
.encode('base64')[:-1])
else:
self.simpleauth_hash = None
def prepare_headers(self, query, body):
headers = { "User-Agent": self.user_agent,
"authorization": "Basic %s" % self.simpleauth_hash }
headers = { "User-Agent": self.user_agent }
if self.simpleauth_hash is not None:
headers["authorization"] = "Basic %s" % self.simpleauth_hash
if body is not None:
headers["content-length"] = len(body)
if query.__dict__.has_key("depth") and query.depth is not None:
@@ -143,13 +150,24 @@ class HTTPQuery(HTTPSimpleQuery):
class HTTPPUT(HTTPQuery):
method = "PUT"
def __init__(self, url, content):
def __init__(self, url, content,
content_type="application/octet-stream",
exclusive=False):
HTTPQuery.__init__(self, url)
self.content = content
self.content_type = content_type
self.exclusive = exclusive
def render(self):
return self.content
def prepare_headers(self):
headers = HTTPQuery.prepare_headers(self)
if self.exclusive:
headers["if-none-match"] = "*"
return headers
class HTTPPOST(HTTPPUT):
method = "POST"
@@ -162,7 +180,6 @@ class WebDAVQuery(HTTPQuery):
self.depth = depth
self.ns_mgr = _WD_XMLNS_MGR()
self.top_node = None
self.xpath_namespace = { "D": xmlns_dav }
# helper for PROPFIND and REPORT (only)
def _initProperties(self, properties):
@@ -200,17 +217,9 @@ class WebDAVQuery(HTTPQuery):
and (headers["content-type"].startswith("application/xml")
or headers["content-type"].startswith("text/xml"))
and int(headers["content-length"]) > 0):
reader = xml.dom.ext.reader.Sax2.Reader()
tree = xml.etree.ElementTree.ElementTree()
stream = cStringIO.StringIO(self.response["body"])
dom_response = reader.fromStream(stream)
self.response["document"] = dom_response.documentElement
def xpath_evaluate(self, query, top_node = None):
if top_node is None:
top_node = self.response["document"]
xpath_context = xml.xpath.CreateContext(top_node)
xpath_context.setNamespaces(self.xpath_namespace)
return xml.xpath.Evaluate(query, None, xpath_context)
self.response["document"] = tree.parse(stream)
class WebDAVMKCOL(WebDAVQuery):
method = "MKCOL"
@@ -262,20 +271,6 @@ class WebDAVMOVE(WebDAVQuery):
headers["Host"] = self.host
return headers
class WebDAVPUT(WebDAVQuery):
method = "PUT"
def __init__(self, url, content):
WebDAVQuery.__init__(self, url)
self.content_type = "text/plain; charset=utf-8"
self.content = content
def prepare_headers(self):
return WebDAVQuery.prepare_headers(self)
def render(self):
return self.content
class WebDAVPrincipalPropertySearch(WebDAVREPORT):
def __init__(self, url, properties, matches):
WebDAVQuery.__init__(self, url)
+15 -6
View File
@@ -11,7 +11,7 @@
superclass = "SoComponent";
protectedBy = "<public>";
defaultRoles = {
"View" = ( "Authenticated" );
"View" = ( "Authenticated", "PublicUser" );
};
};
SOGoObject = {
@@ -67,10 +67,10 @@
};
SOGoParentFolder = {
superclass = "SOGoFolder";
protectedBy = "Access Contents Information";
protectedBy = "<public>";
defaultRoles = {
"Access Contents Information" = ( "Authenticated" );
"WebDAV Access" = ( "Authenticated" );
"Access Contents Information" = ( "Authenticated", "PublicUser" );
"WebDAV Access" = ( "Authenticated", "PublicUser" );
"Add Folders" = ( "Owner" );
};
};
@@ -78,14 +78,23 @@
superclass = "SOGoFolder";
protectedBy = "Access Contents Information";
defaultRoles = {
"Access Contents Information" = ( "Authenticated" );
"WebDAV Access" = ( "Authenticated" );
"Access Contents Information" = ( "Authenticated", "PublicUser" );
"WebDAV Access" = ( "Authenticated", "PublicUser" );
"View" = ( "Authenticated" );
};
};
SOGoGCSFolder = {
superclass = "SOGoFolder";
};
SOGoPublicBaseFolder = {
superclass = "SOGoFolder";
protectedBy = "Access Contents Information";
defaultRoles = {
"Access Contents Information" = ( "Authenticated", "PublicUser" );
"WebDAV Access" = ( "Authenticated", "PublicUser" );
"View" = ( "Authenticated" );
};
};
};
categories = {