mirror of
https://github.com/inverse-inc/sogo.git
synced 2026-02-25 19:26:24 +00:00
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:
@@ -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
2
NEWS
@@ -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
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 */
|
||||
|
||||
Reference in New Issue
Block a user