mirror of
https://github.com/inverse-inc/sogo.git
synced 2026-05-04 02:55:26 +00:00
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:
@@ -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
@@ -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])
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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" );
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -86,6 +86,7 @@ SOGo_OBJC_FILES = \
|
||||
SOGoFolder.m \
|
||||
SOGoGCSFolder.m \
|
||||
SOGoParentFolder.m \
|
||||
SOGoPublicBaseFolder.m \
|
||||
SOGoUserFolder.m \
|
||||
\
|
||||
SOGoDefaultsSource.m \
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -40,7 +40,6 @@
|
||||
- (NSString *) realNameInContainer;
|
||||
|
||||
- (NSString *) folderType;
|
||||
- (NSArray *) fetchContentObjectNames;
|
||||
|
||||
- (BOOL) isValidContentName: (NSString *) name;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -85,8 +85,6 @@
|
||||
- (id) createChildComponentWithName: (NSString *) newName
|
||||
andContent: (NSString *) newContent;
|
||||
|
||||
- (NSArray *) fetchContentObjectNames;
|
||||
|
||||
/* folder type */
|
||||
|
||||
- (BOOL) folderIsMandatory;
|
||||
|
||||
@@ -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: @"/"];
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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 */
|
||||
@@ -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
|
||||
@@ -64,6 +64,8 @@
|
||||
|
||||
- (NSString *) CASServiceURL;
|
||||
|
||||
- (BOOL) enablePublicAccess;
|
||||
|
||||
@end
|
||||
|
||||
#endif /* SOGOSYSTEMDEFAULTS_H */
|
||||
|
||||
@@ -296,4 +296,9 @@ BootstrapNSUserDefaults ()
|
||||
return [self stringForKey: @"SOGoCASServiceURL"];
|
||||
}
|
||||
|
||||
- (BOOL) enablePublicAccess
|
||||
{
|
||||
return [self boolForKey: @"SOGoEnablePublicAccess"];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -110,7 +110,7 @@
|
||||
SOGoUser *user;
|
||||
|
||||
user = (SOGoUser *) [super userInContext: _ctx];
|
||||
if (!user)
|
||||
if (!user || [[user login] isEqualToString: @"anonymous"])
|
||||
{
|
||||
if (!anonymous)
|
||||
anonymous = [[SOGoUser alloc]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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("."):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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__":
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
@@ -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 = {
|
||||
|
||||
Reference in New Issue
Block a user