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"]
|=======================================================================
.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 */