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 committed 2017-11-16 21:46:44 -05:00
1 parent 08ec68c07c
commit a2129f3e4a
5 files changed
+168 -108

No files matched your search

+18 -1
View File
@@ -1612,7 +1612,7 @@ SQL source:
[cols="3,47a,50"] [cols="3,47a,50"]
|======================================================================= |=======================================================================
.18+^|D |SOGoUserSources .20+^|D |SOGoUserSources
|Parameter used to set the SQL and/or LDAP sources used for |Parameter used to set the SQL and/or LDAP sources used for
authentication and global address books. Multiple sources can be authentication and global address books. Multiple sources can be
specified as an array of dictionaries. A dictionary that defines a SQL 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 See the _Multi-domains Configuration_ section in this document for more
information. 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 Here is an example of an SQL-based authentication and address book
+2
View File
@@ -3,6 +3,8 @@
New features New features
- [core] can now invite attendees to exceptions only (#2561) - [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] display freebusy information of owner in appointment editor
- [web] register SOGo as a handler for the mailto scheme (#1223) - [web] register SOGo as a handler for the mailto scheme (#1223)
- [web] new events list view where events are grouped by day - [web] new events list view where events are grouped by day
+1 -2
View File
@@ -229,8 +229,7 @@ static Class NSStringK;
else else
queryTimeout = [dd ldapQueryTimeout]; queryTimeout = [dd ldapQueryTimeout];
ASSIGN(modulesConstraints, ASSIGN(modulesConstraints, [udSource objectForKey: @"ModulesConstraints"]);
[udSource objectForKey: @"ModulesConstraints"]);
ASSIGN(_filter, [udSource objectForKey: @"filter"]); ASSIGN(_filter, [udSource objectForKey: @"filter"]);
ASSIGN(_userPasswordAlgorithm, [udSource objectForKey: @"userPasswordAlgorithm"]); ASSIGN(_userPasswordAlgorithm, [udSource objectForKey: @"userPasswordAlgorithm"]);
ASSIGN(_scope, ([udSource objectForKey: @"scope"] ASSIGN(_scope, ([udSource objectForKey: @"scope"]
+5 -1
View File
@@ -1,6 +1,6 @@
/* SQLSource.h - this file is part of SOGo /* 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> * Authors: Ludovic Marcotte <lmarcotte@inverse.ca>
* Francis Lachapelle <flachapelle@invers.ca> * Francis Lachapelle <flachapelle@invers.ca>
@@ -50,6 +50,10 @@
/* resources handling */ /* resources handling */
NSString *_kindField; NSString *_kindField;
NSString *_multipleBookingsField; NSString *_multipleBookingsField;
BOOL _listRequiresDot;
NSDictionary *_modulesConstraints;
} }
@end @end
+142 -104
View File
@@ -57,7 +57,7 @@
* *
* A SQL source can be defined like this: * A SQL source can be defined like this:
* *
* { * {
* id = zot; * id = zot;
* type = sql; * type = sql;
* viewURL = "mysql://sogo:sogo@127.0.0.1:5432/sogo/sogo_view"; * viewURL = "mysql://sogo:sogo@127.0.0.1:5432/sogo/sogo_view";
@@ -96,6 +96,8 @@
_multipleBookingsField = nil; _multipleBookingsField = nil;
_imapHostField = nil; _imapHostField = nil;
_sieveHostField = nil; _sieveHostField = nil;
_listRequiresDot = YES;
_modulesConstraints = nil;
} }
return self; return self;
@@ -114,13 +116,16 @@
[_domainField release]; [_domainField release];
[_imapHostField release]; [_imapHostField release];
[_sieveHostField release]; [_sieveHostField release];
[_modulesConstraints release];
[super dealloc]; [super dealloc];
} }
- (id) initFromUDSource: (NSDictionary *) udSource - (id) initFromUDSource: (NSDictionary *) udSource
inDomain: (NSString *) sourceDomain inDomain: (NSString *) sourceDomain
{ {
NSNumber *dotValue;
self = [self init]; self = [self init];
ASSIGN(_sourceID, [udSource objectForKey: @"id"]); ASSIGN(_sourceID, [udSource objectForKey: @"id"]);
@@ -134,17 +139,22 @@
ASSIGN(_kindField, [udSource objectForKey: @"KindFieldName"]); ASSIGN(_kindField, [udSource objectForKey: @"KindFieldName"]);
ASSIGN(_multipleBookingsField, [udSource objectForKey: @"MultipleBookingsFieldName"]); ASSIGN(_multipleBookingsField, [udSource objectForKey: @"MultipleBookingsFieldName"]);
ASSIGN(_domainField, [udSource objectForKey: @"DomainFieldName"]); ASSIGN(_domainField, [udSource objectForKey: @"DomainFieldName"]);
ASSIGN(_modulesConstraints, [udSource objectForKey: @"ModulesConstraints"]);
if ([udSource objectForKey: @"prependPasswordScheme"]) if ([udSource objectForKey: @"prependPasswordScheme"])
_prependPasswordScheme = [[udSource objectForKey: @"prependPasswordScheme"] boolValue]; _prependPasswordScheme = [[udSource objectForKey: @"prependPasswordScheme"] boolValue];
else else
_prependPasswordScheme = NO; _prependPasswordScheme = NO;
if (!_userPasswordAlgorithm) if (!_userPasswordAlgorithm)
_userPasswordAlgorithm = @"none"; _userPasswordAlgorithm = @"none";
if ([udSource objectForKey: @"viewURL"]) if ([udSource objectForKey: @"viewURL"])
_viewURL = [[NSURL alloc] initWithString: [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 #warning this domain code has no effect yet
if ([sourceDomain length]) if ([sourceDomain length])
ASSIGN (_domain, sourceDomain); ASSIGN (_domain, sourceDomain);
@@ -183,7 +193,7 @@
{ {
NSString *pass; NSString *pass;
NSString* result; NSString* result;
pass = [plainPassword asCryptedPassUsingScheme: _userPasswordAlgorithm]; pass = [plainPassword asCryptedPassUsingScheme: _userPasswordAlgorithm];
if (pass == nil) if (pass == nil)
@@ -264,7 +274,7 @@
@" WHERE ", @" WHERE ",
[_viewURL gcsTableName]]; [_viewURL gcsTableName]];
if (_authenticationFilter) if (_authenticationFilter)
{ {
qualifier = [[EOAndQualifier alloc] initWithQualifiers: qualifier = [[EOAndQualifier alloc] initWithQualifiers:
qualifier, qualifier,
[EOQualifier qualifierWithQualifierFormat: _authenticationFilter], [EOQualifier qualifierWithQualifierFormat: _authenticationFilter],
@@ -272,18 +282,18 @@
[qualifier autorelease]; [qualifier autorelease];
} }
[qualifier _gcsAppendToString: sql]; [qualifier _gcsAppendToString: sql];
ex = [channel evaluateExpressionX: sql]; ex = [channel evaluateExpressionX: sql];
if (!ex) if (!ex)
{ {
NSDictionary *row; NSDictionary *row;
NSArray *attrs; NSArray *attrs;
NSString *value; NSString *value;
attrs = [channel describeResults: NO]; attrs = [channel describeResults: NO];
row = [channel fetchAttributes: attrs withZone: NULL]; row = [channel fetchAttributes: attrs withZone: NULL];
value = [row objectForKey: @"c_password"]; value = [row objectForKey: @"c_password"];
rc = [self _isPassword: _pwd equalTo: value]; rc = [self _isPassword: _pwd equalTo: value];
[channel cancelFetch]; [channel cancelFetch];
} }
@@ -308,9 +318,9 @@
* @return YES if the password was successfully changed. * @return YES if the password was successfully changed.
*/ */
- (BOOL) changePasswordForLogin: (NSString *) login - (BOOL) changePasswordForLogin: (NSString *) login
oldPassword: (NSString *) oldPassword oldPassword: (NSString *) oldPassword
newPassword: (NSString *) newPassword newPassword: (NSString *) newPassword
perr: (SOGoPasswordPolicyError *) perr perr: (SOGoPasswordPolicyError *) perr
{ {
EOAdaptorChannel *channel; EOAdaptorChannel *channel;
GCSChannelManager *cm; GCSChannelManager *cm;
@@ -339,10 +349,10 @@
if (channel) if (channel)
{ {
sqlstr = [NSString stringWithFormat: (@"UPDATE %@" sqlstr = [NSString stringWithFormat: (@"UPDATE %@"
@" SET c_password = '%@'" @" SET c_password = '%@'"
@" WHERE c_uid = '%@'"), @" WHERE c_uid = '%@'"),
[_viewURL gcsTableName], encryptedPassword, login]; [_viewURL gcsTableName], encryptedPassword, login];
ex = [channel evaluateExpressionX: sqlstr]; ex = [channel evaluateExpressionX: sqlstr];
if (!ex) if (!ex)
{ {
@@ -355,13 +365,13 @@
[cm releaseChannel: channel]; [cm releaseChannel: channel];
} }
} }
return didChange; return didChange;
} }
- (NSString *) _whereClauseFromArray: (NSArray *) theArray - (NSString *) _whereClauseFromArray: (NSArray *) theArray
value: (NSString *) theValue value: (NSString *) theValue
exact: (BOOL) theBOOL exact: (BOOL) theBOOL
{ {
NSMutableString *s; NSMutableString *s;
int i; int i;
@@ -379,6 +389,35 @@
return s; 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 - (NSDictionary *) _lookupContactEntry: (NSString *) theID
considerEmail: (BOOL) b considerEmail: (BOOL) b
inDomain: (NSString *) domain inDomain: (NSString *) domain
@@ -425,7 +464,7 @@
} }
} }
} }
domainQualifier = nil; domainQualifier = nil;
if (_domainField && domain) if (_domainField && domain)
{ {
@@ -443,7 +482,7 @@
value: [theID lowercaseString]]; value: [theID lowercaseString]];
[loginQualifier autorelease]; [loginQualifier autorelease];
[qualifiers addObject: loginQualifier]; [qualifiers addObject: loginQualifier];
if (_mailFields) if (_mailFields)
{ {
for (i = 0; i < [_mailFields count]; i++) for (i = 0; i < [_mailFields count]; i++)
@@ -461,7 +500,7 @@
} }
} }
} }
sql = [NSMutableString stringWithFormat: @"SELECT *" sql = [NSMutableString stringWithFormat: @"SELECT *"
@" FROM %@" @" FROM %@"
@" WHERE ", @" WHERE ",
@@ -491,12 +530,9 @@
forKey: [field substringFromIndex: 2]]; forKey: [field substringFromIndex: 2]];
} }
// FIXME [self _fillConstraintsForModule: @"Calendar" intoRecord: response];
// We have to do this here since we do not manage modules [self _fillConstraintsForModule: @"Mail" intoRecord: response];
// constraints right now over a SQL backend. [self _fillConstraintsForModule: @"ActiveSync" intoRecord: response];
[response setObject: [NSNumber numberWithBool: YES] forKey: @"CalendarAccess"];
[response setObject: [NSNumber numberWithBool: YES] forKey: @"MailAccess"];
[response setObject: [NSNumber numberWithBool: YES] forKey: @"ActiveSyncAccess"];
// We set the domain, if any // We set the domain, if any
value = nil; value = nil;
@@ -510,7 +546,7 @@
// We populate all mail fields // We populate all mail fields
emails = [NSMutableArray array]; emails = [NSMutableArray array];
if ([response objectForKey: @"mail"]) if ([response objectForKey: @"mail"])
[emails addObject: [response objectForKey: @"mail"]]; [emails addObject: [response objectForKey: @"mail"]];
@@ -520,12 +556,12 @@
int i; int i;
for (i = 0; i < [_mailFields count]; 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 isNotNull] &&
[[s stringByTrimmingSpaces] length] > 0) [[s stringByTrimmingSpaces] length] > 0)
[emails addObjectsFromArray: [s componentsSeparatedByString: @" "]]; [emails addObjectsFromArray: [s componentsSeparatedByString: @" "]];
} }
[response setObject: emails forKey: @"c_emails"]; [response setObject: emails forKey: @"c_emails"];
if (_imapHostField) if (_imapHostField)
{ {
@@ -576,7 +612,7 @@
} }
else else
[response setObject: [NSNumber numberWithBool: YES] forKey: @"canAuthenticate"]; [response setObject: [NSNumber numberWithBool: YES] forKey: @"canAuthenticate"];
// We check if we should use a different login for IMAP // We check if we should use a different login for IMAP
if (_imapLoginField) if (_imapLoginField)
{ {
@@ -586,26 +622,26 @@
// We check if it's a resource of not // We check if it's a resource of not
if (_kindField) if (_kindField)
{ {
if ((value = [response objectForKey: _kindField]) && [value isNotNull]) if ((value = [response objectForKey: _kindField]) && [value isNotNull])
{ {
if ([value caseInsensitiveCompare: @"location"] == NSOrderedSame || if ([value caseInsensitiveCompare: @"location"] == NSOrderedSame ||
[value caseInsensitiveCompare: @"thing"] == NSOrderedSame || [value caseInsensitiveCompare: @"thing"] == NSOrderedSame ||
[value caseInsensitiveCompare: @"group"] == NSOrderedSame) [value caseInsensitiveCompare: @"group"] == NSOrderedSame)
{ {
[response setObject: [NSNumber numberWithInt: 1] [response setObject: [NSNumber numberWithInt: 1]
forKey: @"isResource"]; forKey: @"isResource"];
} }
} }
} }
if (_multipleBookingsField) if (_multipleBookingsField)
{ {
if ((value = [response objectForKey: _multipleBookingsField])) if ((value = [response objectForKey: _multipleBookingsField]))
{ {
[response setObject: [NSNumber numberWithInt: [value intValue]] [response setObject: [NSNumber numberWithInt: [value intValue]]
forKey: @"numberOfSimultaneousBookings"]; forKey: @"numberOfSimultaneousBookings"];
} }
} }
[response setObject: self forKey: @"source"]; [response setObject: self forKey: @"source"];
@@ -730,7 +766,7 @@
NSString *value; NSString *value;
attrs = [channel describeResults: NO]; attrs = [channel describeResults: NO];
while ((row = [channel fetchAttributes: attrs withZone: NULL])) while ((row = [channel fetchAttributes: attrs withZone: NULL]))
{ {
value = [row objectForKey: @"c_uid"]; value = [row objectForKey: @"c_uid"];
@@ -746,7 +782,7 @@
[self errorWithFormat:@"failed to acquire channel for URL: %@", [self errorWithFormat:@"failed to acquire channel for URL: %@",
[_viewURL absoluteString]]; [_viewURL absoluteString]];
return results; return results;
} }
@@ -764,73 +800,76 @@
NSException *ex; NSException *ex;
NSMutableString *sql; NSMutableString *sql;
NSString *lowerFilter; NSString *lowerFilter;
results = [NSMutableArray array]; results = [NSMutableArray array];
cm = [GCSChannelManager defaultChannelManager]; if ([filter length] > 0 || !_listRequiresDot)
channel = [cm acquireOpenChannelForURL: _viewURL];
if (channel)
{ {
lowerFilter = [filter lowercaseString]; cm = [GCSChannelManager defaultChannelManager];
lowerFilter = [lowerFilter stringByReplacingString: @"'" withString: @"''"]; channel = [cm acquireOpenChannelForURL: _viewURL];
if (channel)
sql = [NSMutableString stringWithFormat: (@"SELECT *"
@" FROM %@"
@" WHERE"
@" (LOWER(c_cn) LIKE '%%%@%%'"
@" OR LOWER(mail) LIKE '%%%@%%'"),
[_viewURL gcsTableName],
lowerFilter, lowerFilter];
if (_mailFields && [_mailFields count] > 0)
{ {
[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 (_mailFields && [_mailFields count] > 0)
{
if ([domain length])
{ {
EOQualifier *domainQualifier; [sql appendString: [self _whereClauseFromArray: _mailFields value: lowerFilter exact: NO]];
domainQualifier = }
[self _visibleDomainsQualifierFromDomain: domain];
if (domainQualifier) [sql appendString: @")"];
if (_domainField)
{
if ([domain length])
{ {
[sql appendFormat: @" AND ("]; EOQualifier *domainQualifier;
[domainQualifier _gcsAppendToString: sql]; domainQualifier =
[sql appendFormat: @")"]; [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 else
[sql appendFormat: @" AND %@ IS NULL", _domainField]; [self errorWithFormat: @"could not run SQL '%@': %@", sql, ex];
} [cm releaseChannel: channel];
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 else
[self errorWithFormat: @"could not run SQL '%@': %@", sql, ex]; [self errorWithFormat:@"failed to acquire channel for URL: %@",
[cm releaseChannel: channel]; [_viewURL absoluteString]];
} }
else
[self errorWithFormat:@"failed to acquire channel for URL: %@",
[_viewURL absoluteString]];
return results; return results;
} }
@@ -856,13 +895,12 @@
- (void) setListRequiresDot: (BOOL) newListRequiresDot - (void) setListRequiresDot: (BOOL) newListRequiresDot
{ {
_listRequiresDot = newListRequiresDot;
} }
- (BOOL) listRequiresDot - (BOOL) listRequiresDot
{ {
/* This method is not implemented for SQLSource. It must enable a mechanism return _listRequiresDot;
where using "." is not required to list the content of addressbooks. */
return YES;
} }
/* card editing */ /* card editing */