ModulesConstraints and listRequiresDot for SQL

SQL sources used for authentication can now have module constraints.
Entries of SQL sources used as address books can now be displayed
automatically.
This commit is contained in:
Francis Lachapelle
2017-11-16 21:33:29 -05:00
parent 08ec68c07c
commit a2129f3e4a
5 changed files with 168 additions and 108 deletions

View File

@@ -1612,7 +1612,7 @@ SQL source:
[cols="3,47a,50"]
|=======================================================================
.18+^|D |SOGoUserSources
.20+^|D |SOGoUserSources
|Parameter used to set the SQL and/or LDAP sources used for
authentication and global address books. Multiple sources can be
specified as an array of dictionaries. A dictionary that defines a SQL
@@ -1719,6 +1719,23 @@ to the user.
See the _Multi-domains Configuration_ section in this document for more
information.
|listRequiresDot (optional)
|If set to `YES`, listing of this SQL source is only possible when performing a search (respecting the SOGoSearchMinimumWordLength parameter) or when explicitely typing a single dot.
Defaults to `YES` when unset.
|ModulesConstraints (optional)
|Limits the access of any module through a constraint based on a SQL
column; must be a dictionary with keys `Mail`, and/or `Calendar`,
and/or `ActiveSync` for example:
----
ModulesConstraints = {
Calendar = {
c_ou = employees;
};
};
----
|=======================================================================
Here is an example of an SQL-based authentication and address book

2
NEWS
View File

@@ -3,6 +3,8 @@
New features
- [core] can now invite attendees to exceptions only (#2561)
- [core] add support for module constraints in SQL sources
- [core] add support for listRequiresDot in SQL sources
- [web] display freebusy information of owner in appointment editor
- [web] register SOGo as a handler for the mailto scheme (#1223)
- [web] new events list view where events are grouped by day

View File

@@ -229,8 +229,7 @@ static Class NSStringK;
else
queryTimeout = [dd ldapQueryTimeout];
ASSIGN(modulesConstraints,
[udSource objectForKey: @"ModulesConstraints"]);
ASSIGN(modulesConstraints, [udSource objectForKey: @"ModulesConstraints"]);
ASSIGN(_filter, [udSource objectForKey: @"filter"]);
ASSIGN(_userPasswordAlgorithm, [udSource objectForKey: @"userPasswordAlgorithm"]);
ASSIGN(_scope, ([udSource objectForKey: @"scope"]

View File

@@ -1,6 +1,6 @@
/* SQLSource.h - this file is part of SOGo
*
* Copyright (C) 2009-2011 Inverse inc.
* Copyright (C) 2009-2017 Inverse inc.
*
* Authors: Ludovic Marcotte <lmarcotte@inverse.ca>
* Francis Lachapelle <flachapelle@invers.ca>
@@ -50,6 +50,10 @@
/* resources handling */
NSString *_kindField;
NSString *_multipleBookingsField;
BOOL _listRequiresDot;
NSDictionary *_modulesConstraints;
}
@end

View File

@@ -57,7 +57,7 @@
*
* A SQL source can be defined like this:
*
* {
* {
* id = zot;
* type = sql;
* viewURL = "mysql://sogo:sogo@127.0.0.1:5432/sogo/sogo_view";
@@ -96,6 +96,8 @@
_multipleBookingsField = nil;
_imapHostField = nil;
_sieveHostField = nil;
_listRequiresDot = YES;
_modulesConstraints = nil;
}
return self;
@@ -114,13 +116,16 @@
[_domainField release];
[_imapHostField release];
[_sieveHostField release];
[_modulesConstraints release];
[super dealloc];
}
- (id) initFromUDSource: (NSDictionary *) udSource
inDomain: (NSString *) sourceDomain
{
NSNumber *dotValue;
self = [self init];
ASSIGN(_sourceID, [udSource objectForKey: @"id"]);
@@ -134,17 +139,22 @@
ASSIGN(_kindField, [udSource objectForKey: @"KindFieldName"]);
ASSIGN(_multipleBookingsField, [udSource objectForKey: @"MultipleBookingsFieldName"]);
ASSIGN(_domainField, [udSource objectForKey: @"DomainFieldName"]);
ASSIGN(_modulesConstraints, [udSource objectForKey: @"ModulesConstraints"]);
if ([udSource objectForKey: @"prependPasswordScheme"])
_prependPasswordScheme = [[udSource objectForKey: @"prependPasswordScheme"] boolValue];
else
_prependPasswordScheme = NO;
if (!_userPasswordAlgorithm)
_userPasswordAlgorithm = @"none";
if ([udSource objectForKey: @"viewURL"])
_viewURL = [[NSURL alloc] initWithString: [udSource objectForKey: @"viewURL"]];
dotValue = [udSource objectForKey: @"listRequiresDot"];
if (dotValue)
[self setListRequiresDot: [dotValue boolValue]];
#warning this domain code has no effect yet
if ([sourceDomain length])
ASSIGN (_domain, sourceDomain);
@@ -183,7 +193,7 @@
{
NSString *pass;
NSString* result;
pass = [plainPassword asCryptedPassUsingScheme: _userPasswordAlgorithm];
if (pass == nil)
@@ -264,7 +274,7 @@
@" WHERE ",
[_viewURL gcsTableName]];
if (_authenticationFilter)
{
{
qualifier = [[EOAndQualifier alloc] initWithQualifiers:
qualifier,
[EOQualifier qualifierWithQualifierFormat: _authenticationFilter],
@@ -272,18 +282,18 @@
[qualifier autorelease];
}
[qualifier _gcsAppendToString: sql];
ex = [channel evaluateExpressionX: sql];
if (!ex)
{
NSDictionary *row;
NSArray *attrs;
NSString *value;
attrs = [channel describeResults: NO];
row = [channel fetchAttributes: attrs withZone: NULL];
value = [row objectForKey: @"c_password"];
rc = [self _isPassword: _pwd equalTo: value];
[channel cancelFetch];
}
@@ -308,9 +318,9 @@
* @return YES if the password was successfully changed.
*/
- (BOOL) changePasswordForLogin: (NSString *) login
oldPassword: (NSString *) oldPassword
newPassword: (NSString *) newPassword
perr: (SOGoPasswordPolicyError *) perr
oldPassword: (NSString *) oldPassword
newPassword: (NSString *) newPassword
perr: (SOGoPasswordPolicyError *) perr
{
EOAdaptorChannel *channel;
GCSChannelManager *cm;
@@ -339,10 +349,10 @@
if (channel)
{
sqlstr = [NSString stringWithFormat: (@"UPDATE %@"
@" SET c_password = '%@'"
@" WHERE c_uid = '%@'"),
[_viewURL gcsTableName], encryptedPassword, login];
@" SET c_password = '%@'"
@" WHERE c_uid = '%@'"),
[_viewURL gcsTableName], encryptedPassword, login];
ex = [channel evaluateExpressionX: sqlstr];
if (!ex)
{
@@ -355,13 +365,13 @@
[cm releaseChannel: channel];
}
}
return didChange;
}
- (NSString *) _whereClauseFromArray: (NSArray *) theArray
value: (NSString *) theValue
exact: (BOOL) theBOOL
exact: (BOOL) theBOOL
{
NSMutableString *s;
int i;
@@ -379,6 +389,35 @@
return s;
}
- (void) _fillConstraintsForModule: (NSString *) module
intoRecord: (NSMutableDictionary *) record
{
NSDictionary *constraints;
NSEnumerator *matches;
NSString *currentMatch, *currentValue, *recordValue;
BOOL result;
result = YES;
constraints = [_modulesConstraints objectForKey: module];
if (constraints)
{
matches = [[constraints allKeys] objectEnumerator];
while (result == YES && (currentMatch = [matches nextObject]))
{
currentValue = [constraints objectForKey: currentMatch];
recordValue = [record objectForKey: currentMatch];
result = NO;
if ([recordValue caseInsensitiveMatches: currentValue])
result = YES;
}
}
[record setObject: [NSNumber numberWithBool: result]
forKey: [NSString stringWithFormat: @"%@Access", module]];
}
- (NSDictionary *) _lookupContactEntry: (NSString *) theID
considerEmail: (BOOL) b
inDomain: (NSString *) domain
@@ -425,7 +464,7 @@
}
}
}
domainQualifier = nil;
if (_domainField && domain)
{
@@ -443,7 +482,7 @@
value: [theID lowercaseString]];
[loginQualifier autorelease];
[qualifiers addObject: loginQualifier];
if (_mailFields)
{
for (i = 0; i < [_mailFields count]; i++)
@@ -461,7 +500,7 @@
}
}
}
sql = [NSMutableString stringWithFormat: @"SELECT *"
@" FROM %@"
@" WHERE ",
@@ -491,12 +530,9 @@
forKey: [field substringFromIndex: 2]];
}
// FIXME
// We have to do this here since we do not manage modules
// constraints right now over a SQL backend.
[response setObject: [NSNumber numberWithBool: YES] forKey: @"CalendarAccess"];
[response setObject: [NSNumber numberWithBool: YES] forKey: @"MailAccess"];
[response setObject: [NSNumber numberWithBool: YES] forKey: @"ActiveSyncAccess"];
[self _fillConstraintsForModule: @"Calendar" intoRecord: response];
[self _fillConstraintsForModule: @"Mail" intoRecord: response];
[self _fillConstraintsForModule: @"ActiveSync" intoRecord: response];
// We set the domain, if any
value = nil;
@@ -510,7 +546,7 @@
// We populate all mail fields
emails = [NSMutableArray array];
if ([response objectForKey: @"mail"])
[emails addObject: [response objectForKey: @"mail"]];
@@ -520,12 +556,12 @@
int i;
for (i = 0; i < [_mailFields count]; i++)
if ((s = [response objectForKey: [_mailFields objectAtIndex: i]]) &&
if ((s = [response objectForKey: [_mailFields objectAtIndex: i]]) &&
[s isNotNull] &&
[[s stringByTrimmingSpaces] length] > 0)
[emails addObjectsFromArray: [s componentsSeparatedByString: @" "]];
[[s stringByTrimmingSpaces] length] > 0)
[emails addObjectsFromArray: [s componentsSeparatedByString: @" "]];
}
[response setObject: emails forKey: @"c_emails"];
if (_imapHostField)
{
@@ -576,7 +612,7 @@
}
else
[response setObject: [NSNumber numberWithBool: YES] forKey: @"canAuthenticate"];
// We check if we should use a different login for IMAP
if (_imapLoginField)
{
@@ -586,26 +622,26 @@
// We check if it's a resource of not
if (_kindField)
{
{
if ((value = [response objectForKey: _kindField]) && [value isNotNull])
{
if ([value caseInsensitiveCompare: @"location"] == NSOrderedSame ||
[value caseInsensitiveCompare: @"thing"] == NSOrderedSame ||
[value caseInsensitiveCompare: @"group"] == NSOrderedSame)
{
[response setObject: [NSNumber numberWithInt: 1]
forKey: @"isResource"];
}
}
{
if ([value caseInsensitiveCompare: @"location"] == NSOrderedSame ||
[value caseInsensitiveCompare: @"thing"] == NSOrderedSame ||
[value caseInsensitiveCompare: @"group"] == NSOrderedSame)
{
[response setObject: [NSNumber numberWithInt: 1]
forKey: @"isResource"];
}
}
}
if (_multipleBookingsField)
{
if ((value = [response objectForKey: _multipleBookingsField]))
{
[response setObject: [NSNumber numberWithInt: [value intValue]]
forKey: @"numberOfSimultaneousBookings"];
}
{
[response setObject: [NSNumber numberWithInt: [value intValue]]
forKey: @"numberOfSimultaneousBookings"];
}
}
[response setObject: self forKey: @"source"];
@@ -730,7 +766,7 @@
NSString *value;
attrs = [channel describeResults: NO];
while ((row = [channel fetchAttributes: attrs withZone: NULL]))
{
value = [row objectForKey: @"c_uid"];
@@ -746,7 +782,7 @@
[self errorWithFormat:@"failed to acquire channel for URL: %@",
[_viewURL absoluteString]];
return results;
}
@@ -764,73 +800,76 @@
NSException *ex;
NSMutableString *sql;
NSString *lowerFilter;
results = [NSMutableArray array];
cm = [GCSChannelManager defaultChannelManager];
channel = [cm acquireOpenChannelForURL: _viewURL];
if (channel)
if ([filter length] > 0 || !_listRequiresDot)
{
lowerFilter = [filter lowercaseString];
lowerFilter = [lowerFilter stringByReplacingString: @"'" withString: @"''"];
sql = [NSMutableString stringWithFormat: (@"SELECT *"
@" FROM %@"
@" WHERE"
@" (LOWER(c_cn) LIKE '%%%@%%'"
@" OR LOWER(mail) LIKE '%%%@%%'"),
[_viewURL gcsTableName],
lowerFilter, lowerFilter];
if (_mailFields && [_mailFields count] > 0)
cm = [GCSChannelManager defaultChannelManager];
channel = [cm acquireOpenChannelForURL: _viewURL];
if (channel)
{
[sql appendString: [self _whereClauseFromArray: _mailFields value: lowerFilter exact: NO]];
}
lowerFilter = [filter lowercaseString];
lowerFilter = [lowerFilter stringByReplacingString: @"'" withString: @"''"];
[sql appendString: @")"];
sql = [NSMutableString stringWithFormat: (@"SELECT *"
@" FROM %@"
@" WHERE"
@" (LOWER(c_cn) LIKE '%%%@%%'"
@" OR LOWER(mail) LIKE '%%%@%%'"),
[_viewURL gcsTableName],
lowerFilter, lowerFilter];
if (_domainField)
{
if ([domain length])
if (_mailFields && [_mailFields count] > 0)
{
EOQualifier *domainQualifier;
domainQualifier =
[self _visibleDomainsQualifierFromDomain: domain];
if (domainQualifier)
[sql appendString: [self _whereClauseFromArray: _mailFields value: lowerFilter exact: NO]];
}
[sql appendString: @")"];
if (_domainField)
{
if ([domain length])
{
[sql appendFormat: @" AND ("];
[domainQualifier _gcsAppendToString: sql];
[sql appendFormat: @")"];
EOQualifier *domainQualifier;
domainQualifier =
[self _visibleDomainsQualifierFromDomain: domain];
if (domainQualifier)
{
[sql appendFormat: @" AND ("];
[domainQualifier _gcsAppendToString: sql];
[sql appendFormat: @")"];
}
}
else
[sql appendFormat: @" AND %@ IS NULL", _domainField];
}
ex = [channel evaluateExpressionX: sql];
if (!ex)
{
NSDictionary *row;
NSArray *attrs;
attrs = [channel describeResults: NO];
while ((row = [channel fetchAttributes: attrs withZone: NULL]))
{
row = [row mutableCopy];
[(NSMutableDictionary *) row setObject: self forKey: @"source"];
[results addObject: row];
[row release];
}
}
else
[sql appendFormat: @" AND %@ IS NULL", _domainField];
}
ex = [channel evaluateExpressionX: sql];
if (!ex)
{
NSDictionary *row;
NSArray *attrs;
attrs = [channel describeResults: NO];
while ((row = [channel fetchAttributes: attrs withZone: NULL]))
{
row = [row mutableCopy];
[(NSMutableDictionary *) row setObject: self forKey: @"source"];
[results addObject: row];
[row release];
}
[self errorWithFormat: @"could not run SQL '%@': %@", sql, ex];
[cm releaseChannel: channel];
}
else
[self errorWithFormat: @"could not run SQL '%@': %@", sql, ex];
[cm releaseChannel: channel];
[self errorWithFormat:@"failed to acquire channel for URL: %@",
[_viewURL absoluteString]];
}
else
[self errorWithFormat:@"failed to acquire channel for URL: %@",
[_viewURL absoluteString]];
return results;
}
@@ -856,13 +895,12 @@
- (void) setListRequiresDot: (BOOL) newListRequiresDot
{
_listRequiresDot = newListRequiresDot;
}
- (BOOL) listRequiresDot
{
/* This method is not implemented for SQLSource. It must enable a mechanism
where using "." is not required to list the content of addressbooks. */
return YES;
return _listRequiresDot;
}
/* card editing */