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.
pull/218/merge
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"] [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
NEWS
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

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"]

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

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 */