1
0
mirror of https://github.com/Alinto/sogo.git synced 2025-04-18 10:04:00 +03:00

feat(openID): first stable version

This commit is contained in:
Hivert Quentin 2025-03-13 15:24:03 +01:00
parent 5cba10ccff
commit 458d39d48a
27 changed files with 1789 additions and 77 deletions

View File

@ -437,8 +437,8 @@ Defaults to `300`.
|S |SOGoAuthenticationType
|Parameter used to define the way by which users will be authenticated.
For C.A.S., specify `cas`. For SAML2, specify `saml2`. For anything
else, leave that value empty.
For C.A.S., specify `cas`. For SAML2, specify `saml2`. For opendID Connect, specify `openid`.
For anything else, leave that value empty.
|S |SOGoTrustProxyAuthentication
|Parameter used to set whether HTTP username should be trusted.

View File

@ -288,16 +288,16 @@ static BOOL debugLeaks;
e = [urlStrings objectEnumerator];
while (ok && (tmp = [e nextObject]))
{
value = [defaults stringForKey: tmp];
if (value)
[self _checkTableWithCM: cm tableURL: value andType: tmp];
else
{
value = [defaults stringForKey: tmp];
if (value)
[self _checkTableWithCM: cm tableURL: value andType: tmp];
else
{
[self errorWithFormat: @"No value specified for '%@'", tmp];
ok = NO;
}
[self errorWithFormat: @"No value specified for '%@'", tmp];
ok = NO;
}
}
if (combined)
{
@ -322,9 +322,23 @@ static BOOL debugLeaks;
// Create the email alarms table, if required
if ([defaults enableEMailAlarms])
{
[[fm alarmsFolder] createFolderIfNotExists];
}
{
[[fm alarmsFolder] createFolderIfNotExists];
}
//Create mandatory openId table, if used
if([[defaults authenticationType] isEqualToString: @"openid"])
{
value = [defaults stringForKey: @"OCSOpenIdURL"];
if (value)
[[fm openIdFolder] createFolderIfNotExists];
else
{
[self errorWithFormat: @"No value specified for 'OCSOpenIdURL' for auth mode %@", [defaults authenticationType]];
ok = NO;
}
}
}
return ok;

View File

@ -31,7 +31,7 @@
*/
@class NSString, NSArray, NSURL, NSDictionary, NSException;
@class GCSChannelManager, GCSAlarmsFolder, GCSAdminFolder, GCSFolder, GCSFolderType, GCSSessionsFolder;
@class GCSChannelManager, GCSAlarmsFolder, GCSAdminFolder, GCSFolder, GCSFolderType, GCSSessionsFolder, GCSOpenIdFolder;
@interface GCSFolderManager : NSObject
{
@ -92,6 +92,9 @@
/* admin */
- (GCSAdminFolder *)adminFolder;
/* openid */
- (GCSOpenIdFolder *) openIdFolder;
/* folder types */
- (GCSFolderType *)folderTypeWithName:(NSString *)_name;

View File

@ -38,6 +38,7 @@
#import "EOAdaptorChannel+GCS.h"
#import "GCSAlarmsFolder.h"
#import "GCSAdminFolder.h"
#import "GCSOpenIdFolder.h"
#import "GCSFolder.h"
#import "GCSFolderType.h"
#import "GCSSessionsFolder.h"
@ -504,6 +505,12 @@ static BOOL _singleStoreMode = NO;
return [GCSAdminFolder adminFolderWithFolderManager: self];
}
/* openId */
- (GCSOpenIdFolder *) openIdFolder
{
return [GCSOpenIdFolder openIdFolderWithFolderManager: self];
}
- (NSString *)generateSQLWhereForInternalNames:(NSArray *)_names
exactMatch:(BOOL)_beExact orDirectSubfolderMatch:(BOOL)_directSubs
{

View File

@ -0,0 +1,57 @@
/* GCSAdminFolder.h - this file is part of $PROJECT_NAME_HERE$
*
* Copyright (C) 2023 Alinto
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef GCSOPENIDFOLDER_H
#define GCSOPENIDOLDER_H
@class NSCalendarDate;
@class NSException;
@class NSNumber;
@class NSString;
@class GCSFolderManager;
@interface GCSOpenIdFolder : NSObject
{
GCSFolderManager *folderManager;
}
+ (id)openIdFolderWithFolderManager:(GCSFolderManager *)newFolderManager;
- (void) setFolderManager: (GCSFolderManager *) newFolderManager;
/* operations */
- (void) createFolderIfNotExists;
- (BOOL) canConnectStore;
- (NSString *) getRefreshToken: (NSString *) _user_session useOldSession: (BOOL) use_old_session;
- (NSString *) getNewToken: (NSString *) _old_session;
- (NSException *) writeOpenIdSession: (NSString *) _user_session
withOldSession: (NSString *) _old_session
withRefreshToken: (NSString *) _refresh_token
withExpire: (NSNumber *) _expire
withRefreshExpire: (NSNumber *) _refresh_expire;
- (NSException *) deleteOpenIdSessionFor: (NSString *) _user_session;
@end
#endif /* GCSALARMSFOLDER_H */

View File

@ -0,0 +1,363 @@
/* GCSAdminFolder.m - this file is part of SOGo
*
* Copyright (C) 2023 Alinto
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#import <NGExtensions/NSObject+Logs.h>
#import <NGExtensions/NSNull+misc.h>
#import <GDLAccess/EOAdaptorContext.h>
#import <GDLAccess/EOAttribute.h>
#import <GDLAccess/EOEntity.h>
#import <GDLAccess/EOSQLQualifier.h>
#import "EOQualifier+GCS.h"
#import "GCSChannelManager.h"
#import "GCSFolderManager.h"
#import "GCSSpecialQueries.h"
#import "NSURL+GCS.h"
#import "GCSOpenIdFolder.h"
static NSString *openIdFolderURLString = nil;
#warning GCSOpenIdFolder should share a common ancestor with GCSFolder
@implementation GCSOpenIdFolder
+ (void) initialize
{
NSUserDefaults *ud;
if (!openIdFolderURLString)
{
ud = [NSUserDefaults standardUserDefaults];
ASSIGN (openIdFolderURLString, [ud stringForKey: @"OCSOpenIdURL"]);
}
}
+ (id) openIdFolderWithFolderManager: (GCSFolderManager *) newFolderManager
{
GCSOpenIdFolder *newFolder;
if (openIdFolderURLString)
{
newFolder = [self new];
[newFolder autorelease];
[newFolder setFolderManager: newFolderManager];
}
else
{
[self errorWithFormat: @"'OCSOpenIdURL' is not set"];
newFolder = nil;
}
return newFolder;
}
- (void) setFolderManager: (GCSFolderManager *) newFolderManager
{
ASSIGN (folderManager, newFolderManager);
}
/* accessors */
- (NSURL *) _location
{
NSURL *location;
if (openIdFolderURLString)
location = [NSURL URLWithString: openIdFolderURLString];
else
{
[self warnWithFormat: @"'OCSOpenIdURL' is not set"];
location = nil;
}
return location;
}
- (GCSChannelManager *) _channelManager
{
return [folderManager channelManager];
}
- (NSString *) _storeTableName
{
return [[self _location] gcsTableName];
}
- (EOEntity *) _storeTableEntityForChannel: (EOAdaptorChannel *) tc
{
static EOEntity *entity = nil;
EOAttribute *attribute;
NSString *tableName;
NSString *columns[] = {@"c_user_session", @"c_old_session", @"c_session_started", @"c_refresh_token", @"c_access_token_expires_in", @"c_refresh_token_expires_in", nil };
NSString **column;
NSMutableArray *keys;
NSDictionary *types;
if (!entity)
{
entity = [EOEntity new];
tableName = [self _storeTableName];
[entity setName: tableName];
[entity setExternalName: tableName];
types = [[tc specialQueries] openIdAttributeTypes];
column = columns;
while (*column)
{
attribute = [EOAttribute new];
[attribute setName: *column];
[attribute setColumnName: *column];
[attribute setExternalType: [types objectForKey: *column]];
[entity addAttribute: attribute];
[attribute release];
column++;
}
keys = [NSMutableArray arrayWithCapacity: 1];
[keys addObject: [entity attributeNamed: @"c_user_session"]];
[entity setPrimaryKeyAttributes: keys];
keys = [NSMutableArray arrayWithCapacity: 5];
[keys addObject: [entity attributeNamed: @"c_old_session"]];
[keys addObject: [entity attributeNamed: @"c_session_started"]];
[keys addObject: [entity attributeNamed: @"c_refresh_token"]];
[keys addObject: [entity attributeNamed: @"c_access_token_expires_in"]];
[keys addObject: [entity attributeNamed: @"c_refresh_token_expires_in"]];
[entity setClassProperties: keys];
[entity setAttributesUsedForLocking: [NSArray array]];
}
return entity;
}
/* connection */
- (EOAdaptorChannel *) _acquireStoreChannel
{
return [[self _channelManager] acquireOpenChannelForURL: [self _location]];
}
- (void) _releaseChannel: (EOAdaptorChannel *) _channel
{
[[self _channelManager] releaseChannel:_channel immediately: YES];
}
- (BOOL) canConnectStore
{
return [[self _channelManager] canConnect:[self _location]];
}
- (void) createFolderIfNotExists
{
EOAdaptorChannel *tc;
NSString *sql, *tableName;
GCSSpecialQueries *queries;
tc = [self _acquireStoreChannel];
tableName = [self _storeTableName];
queries = [tc specialQueries];
sql = [NSString stringWithFormat: @"SELECT 1 FROM %@ WHERE 1 = 2", tableName];
if ([tc evaluateExpressionX: sql])
{
sql = [queries createOpenIdFolderWithName: tableName];
if (![tc evaluateExpressionX: sql])
[self logWithFormat: @"openid folder table '%@' successfully created!", tableName];
}
else
[tc cancelFetch];
[self _releaseChannel: tc];
}
/* operations */
/* table has the following fields:
c_user_session varchar(255) NOT NULL,
c_session_started int(11) NOT NULL,
c_refresh_token varchar(4096) DEFAULT '',
c_access_token_expires_in int(11) NOT NULL,
c_refresh_token_expires_in int(11) DEFAULT NULL,
*/
- (NSDictionary *) recordForSession: (NSString *) _user_session useOldSession: (BOOL) use_old_session
{
EOAdaptorChannel *tc;
EOAdaptorContext *context;
NSException *error;
NSArray *attrs;
NSDictionary *record;
EOEntity *entity;
EOSQLQualifier *qualifier;
record = nil;
tc = [self _acquireStoreChannel];
if (tc)
{
context = [tc adaptorContext];
entity = [self _storeTableEntityForChannel: tc];
if(!use_old_session)
qualifier = [[EOSQLQualifier alloc] initWithEntity: entity
qualifierFormat: @"c_user_session='%@'", _user_session];
else
qualifier = [[EOSQLQualifier alloc] initWithEntity: entity
qualifierFormat: @"c_old_session='%@'", _user_session];
[qualifier autorelease];
[context beginTransaction];
error = [tc selectAttributesX: [entity attributesUsedForFetch]
describedByQualifier: qualifier
fetchOrder: nil
lock: NO];
if (error)
[self errorWithFormat:@"%s: cannot execute fetch: %@", __PRETTY_FUNCTION__, error];
else
{
attrs = [tc describeResults: NO];
record = [tc fetchAttributes: attrs withZone: NULL];
[tc cancelFetch];
}
[context rollbackTransaction];
[self _releaseChannel: tc];
}
return record;
}
- (NSString *) getRefreshToken: (NSString *) _user_session
{
NSDictionary *r;
r = [self recordForSession: _user_session useOldSession: NO];
if (r && [r objectForKey:@"c_refresh_token"])
return [r objectForKey:@"c_refresh_token"];
return nil;
}
- (NSString *) getNewToken: (NSString *) _old_session
{
NSDictionary *r;
r = [self recordForSession: _old_session useOldSession: YES];
if (r && [r objectForKey:@"c_user_session"])
return [r objectForKey:@"c_user_session"];
return nil;
}
- (NSException *) writeOpenIdSession: (NSString *) _user_session
withOldSession: (NSString *) _old_session
withRefreshToken: (NSString *) _refresh_token
withExpire: (NSNumber *) _expire
withRefreshExpire: (NSNumber *) _refresh_expire
{
NSDictionary *record, *newRecord;
NSException *error;
NSCalendarDate *nowDate;
int now, nowExpire, nowRefreshExpire;
EOAdaptorChannel *tc;
EOAdaptorContext *context;
EOEntity *entity;
EOSQLQualifier *qualifier;
error = nil;
tc = [self _acquireStoreChannel];
if (tc)
{
context = [tc adaptorContext];
nowDate = [NSCalendarDate date];
now = (nowDate ? (int)[nowDate timeIntervalSince1970] : 0);
nowExpire = now + [_expire intValue];
if(_refresh_expire)
nowRefreshExpire = now + [_refresh_expire intValue];
else
nowRefreshExpire = -1;
if(!_old_session)
_old_session = @"";
newRecord = [NSDictionary dictionaryWithObjectsAndKeys: _user_session, @"c_user_session",
_old_session, @"c_old_session",
[NSNumber numberWithInt:now], @"c_session_started",
_refresh_token, @"c_refresh_token",
[NSNumber numberWithInt:nowExpire] , @"c_access_token_expires_in",
[NSNumber numberWithInt:nowRefreshExpire] , @"c_refresh_token_expires_in",
nil];
record = [self recordForSession: _user_session useOldSession: NO];
entity = [self _storeTableEntityForChannel: tc];
[context beginTransaction];
if (!record)
{
//If the session already exist no need to update it as it is unique
error = [tc insertRowX: newRecord forEntity: entity];
}
if (error)
{
[context rollbackTransaction];
[self errorWithFormat:@"%s: cannot write record: %@", __PRETTY_FUNCTION__, error];
}
else
[context commitTransaction];
[self _releaseChannel: tc];
}
return error;
}
- (NSException *) deleteOpenIdSessionFor: (NSString *) _user_session
{
EOAdaptorChannel *tc;
EOAdaptorContext *context;
EOEntity *entity;
EOSQLQualifier *qualifier;
NSException *error;
error = nil;
tc = [self _acquireStoreChannel];
if (tc)
{
context = [tc adaptorContext];
entity = [self _storeTableEntityForChannel: tc];
qualifier = [[EOSQLQualifier alloc] initWithEntity: entity
qualifierFormat: @"c_user_session='%@'",_user_session];
[qualifier autorelease];
[context beginTransaction];
error = [tc deleteRowsDescribedByQualifierX: qualifier];
if (error)
{
[context rollbackTransaction];
[self errorWithFormat:@"%s: cannot delete record: %@", __PRETTY_FUNCTION__, error];
}
else
[context commitTransaction];
[self _releaseChannel: tc];
}
return error;
}
@end

View File

@ -42,6 +42,9 @@
- (NSString *)createAdminFolderWithName:(NSString *)tableName;
- (NSDictionary *)adminAttributeTypes;
- (NSString *)createOpenIdFolderWithName:(NSString *)tableName;
- (NSDictionary *)openIdAttributeTypes;
- (NSString *) updateCPathInFolderInfo:(NSString *)tableName
withCPath2:(NSString *)c_path2;

View File

@ -95,6 +95,21 @@
return nil;
}
- (NSString *) createOpenIdFolderWithName: (NSString *) tableName
{
[self subclassResponsibility: _cmd];
return nil;
}
- (NSDictionary *) openIdAttributeTypes
{
[self subclassResponsibility: _cmd];
return nil;
}
- (NSString *) createFolderTableWithName: (NSString *) tableName
{
[self subclassResponsibility: _cmd];
@ -195,6 +210,38 @@
return types;
}
- (NSString *) createOpenIdFolderWithName: (NSString *) tableName
{
static NSString *sqlFolderFormat
= (@"CREATE TABLE %@ ("
@" c_user_session VARCHAR(4096) NOT NULL,"
@" c_old_session VARCHAR(4096) NULL,"
@" c_session_started INT4 NOT NULL,"
@" c_refresh_token VARCHAR(4096) NULL,"
@" c_access_token_expires_in INT4 NOT NULL,"
@" c_refresh_token_expires_in INT4 NULL)");
return [NSString stringWithFormat: sqlFolderFormat, tableName];
}
- (NSDictionary *) openIdAttributeTypes
{
static NSMutableDictionary *types = nil;
if (!types)
{
types = [NSMutableDictionary new];
[types setObject: @"varchar" forKey: @"c_user_session"];
[types setObject: @"varchar" forKey: @"c_old_session"];
[types setObject: @"int" forKey: @"c_session_started"];
[types setObject: @"varchar" forKey: @"c_refresh_token"];
[types setObject: @"int" forKey: @"c_access_token_expires_in"];
[types setObject: @"int" forKey: @"c_refresh_token_expires_in"];
}
return types;
}
- (NSString *) createFolderTableWithName: (NSString *) tableName
{
static NSString *sqlFolderFormat
@ -324,6 +371,38 @@
return types;
}
- (NSString *) createOpenIdFolderWithName: (NSString *) tableName
{
static NSString *sqlFolderFormat
= (@"CREATE TABLE %@ ("
@" c_user_session VARCHAR(4096) NOT NULL,"
@" c_old_session VARCHAR(4096) NULL,"
@" c_session_started INT4 NOT NULL,"
@" c_refresh_token VARCHAR(4096) NULL,"
@" c_access_token_expires_in INT4 NOT NULL,"
@" c_refresh_token_expires_in INT4 NULL)");
return [NSString stringWithFormat: sqlFolderFormat, tableName];
}
- (NSDictionary *) openIdAttributeTypes
{
static NSMutableDictionary *types = nil;
if (!types)
{
types = [NSMutableDictionary new];
[types setObject: @"varchar" forKey: @"c_user_session"];
[types setObject: @"varchar" forKey: @"c_old_session"];
[types setObject: @"int" forKey: @"c_session_started"];
[types setObject: @"varchar" forKey: @"c_refresh_token"];
[types setObject: @"int" forKey: @"c_access_token_expires_in"];
[types setObject: @"int" forKey: @"c_refresh_token_expires_in"];
}
return types;
}
- (NSString *) createFolderTableWithName: (NSString *) tableName
{
static NSString *sqlFolderFormat
@ -453,6 +532,38 @@
return types;
}
- (NSString *) createOpenIdFolderWithName: (NSString *) tableName
{
static NSString *sqlFolderFormat
= (@"CREATE TABLE %@ ("
@" c_user_session VARCHAR2(4096) NOT NULL,"
@" c_old_session VARCHAR2(4096) NULL,"
@" c_session_started INTEGER NOT NULL,"
@" c_refresh_token VARCHAR2(4096) NULL,"
@" c_access_token_expires_in INTEGER NOT NULL,"
@" c_refresh_token_expires_in INTEGER NULL)");
return [NSString stringWithFormat: sqlFolderFormat, tableName];
}
- (NSDictionary *) openIdAttributeTypes
{
static NSMutableDictionary *types = nil;
if (!types)
{
types = [NSMutableDictionary new];
[types setObject: @"varchar2" forKey: @"c_user_session"];
[types setObject: @"varchar2" forKey: @"c_old_session"];
[types setObject: @"integer" forKey: @"c_session_started"];
[types setObject: @"varchar2" forKey: @"c_refresh_token"];
[types setObject: @"integer" forKey: @"c_access_token_expires_in"];
[types setObject: @"integer" forKey: @"c_refresh_token_expires_in"];
}
return types;
}
- (NSString *) createFolderTableWithName: (NSString *) tableName
{
static NSString *sqlFolderFormat

View File

@ -28,13 +28,14 @@ libGDLContentStore_HEADER_FILES += \
GCSAdminFolder.h \
GCSContext.h \
GCSFieldInfo.h \
GCSFolder.h \
GCSFolder.h \
GCSFolderManager.h \
GCSFolderType.h \
GCSChannelManager.h \
GCSOpenIdFolder.h \
GCSSessionsFolder.h \
GCSSpecialQueries.h \
GCSStringFormatter.h \
GCSStringFormatter.h \
libGDLContentStore_OBJC_FILES += \
NSURL+GCS.m \
@ -45,13 +46,14 @@ libGDLContentStore_OBJC_FILES += \
GCSAdminFolder.m \
GCSContext.m \
GCSFieldInfo.m \
GCSFolder.m \
GCSFolder.m \
GCSFolderManager.m \
GCSFolderType.m \
GCSChannelManager.m \
GCSOpenIdFolder.m \
GCSSessionsFolder.m \
GCSSpecialQueries.m \
GCSStringFormatter.m \
GCSStringFormatter.m \
# framework support

View File

@ -165,6 +165,16 @@ CREATE TABLE sogo_admin (
PRIMARY KEY (c_key)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE sogo_opend_id (
c_user_session varchar(4096) NOT NULL,
c_old_session varchar(4096) DEFAULT '',
c_session_started int(11) NOT NULL,
c_refresh_token varchar(4096) DEFAULT '',
c_access_token_expires_in int(11) NOT NULL,
c_refresh_token_expires_in int(11) DEFAULT NULL,
PRIMARY KEY (c_user_session)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC;
CREATE TABLE sogo_user_profile (
c_uid varchar(255) NOT NULL,
c_defaults longtext,

View File

@ -152,7 +152,7 @@
host = [NGInternetSocketAddress addressWithPort:0 onHost:[[self imap4URL] host]];
sd = [SOGoSystemDefaults sharedSystemDefaults];
usesSSO = [[sd authenticationType] isEqualToString: @"cas"] || [[sd authenticationType] isEqualToString: @"saml2"];
usesSSO = [sd isSsoUsed];
if (![[[self mailAccountFolder] nameInContainer] isEqualToString: @"0"] &&
usesSSO &&

View File

@ -69,6 +69,7 @@ SOGo_HEADER_FILES = \
SOGoAuthenticator.h \
SOGoSession.h \
SOGoCASSession.h \
SOGoOpenIdSession.h \
SOGoDAVAuthenticator.h \
SOGoProxyAuthenticator.h \
SOGoStaticAuthenticator.h \
@ -161,6 +162,7 @@ SOGo_OBJC_FILES = \
\
SOGoSession.m \
SOGoCASSession.m \
SOGoOpenIdSession.m \
SOGoDAVAuthenticator.m \
SOGoProxyAuthenticator.m \
SOGoStaticAuthenticator.m \

View File

@ -17,6 +17,7 @@ SOGo_LIBRARIES_DEPEND_UPON += \
-Wl,--no-as-needed \
-L../../SOPE/NGCards/$(GNUSTEP_OBJ_DIR)/ \
-lmemcached \
-lcurl \
-lGDLAccess \
-lNGObjWeb \
-lNGCards \

View File

@ -119,6 +119,13 @@
- (void) removeCASSessionWithTicket: (NSString *) ticket;
// OpenId Support
- (NSString *) openIdSessionFromServer: (NSString *) endpoint;
- (void) setOpenIdSession: (NSString *) openIdSession
forServer: (NSString *) endpoint;
- (NSString *) userOpendIdSessionFromServer: (NSString *) endpoint;
- (void) setUserOpendIdSessionFromServer: (NSString *) endpoint
nextCheckAfter: (NSString *) nextCheck;
//
// SAML2 support
//

View File

@ -683,6 +683,32 @@ static memcached_st *handle = NULL;
}
}
// OpenId support
- (NSString *) openIdSessionFromServer: (NSString *) endpoint
{
return [self valueForKey: [NSString stringWithFormat: @"openId:%@", endpoint]];
}
- (void) setOpenIdSession: (NSString *) openIdSession
forServer: (NSString *) endpoint
{
[self setValue: openIdSession
forKey: [NSString stringWithFormat: @"openId:%@", endpoint]];
}
- (NSString *) userOpendIdSessionFromServer: (NSString *) identifier
{
return [self valueForKey: [NSString stringWithFormat: @"userOpenId:%@", identifier]];
}
- (void) setUserOpendIdSessionFromServer: (NSString *) identifier
nextCheckAfter: (NSString *) nextCheck
{
[self setValue: nextCheck
forKey: [NSString stringWithFormat: @"userOpenId:%@", identifier]];
}
//
// SAML2 support
//

View File

@ -266,6 +266,7 @@
systemMessage: (BOOL) isSystemMessage
{
NSString *currentTo, *login, *password;
NSString * smtpAuthMethod;
NSDictionary *currentAcount;
NSMutableArray *toErrors;
NSEnumerator *addresses;
@ -288,7 +289,9 @@
currentAcount = [[user mailAccounts] objectAtIndex: userId];
//Check if we do an smtp authentication
doSmtpAuth = [authenticationType isEqualToString: @"plain"] && ![authenticator isKindOfClass: [SOGoEmptyAuthenticator class]];
smtpAuthMethod = authenticationType;
doSmtpAuth = ([smtpAuthMethod isEqualToString: @"plain"] || [smtpAuthMethod isEqualToString: @"xoauth2"])
&& ![authenticator isKindOfClass: [SOGoEmptyAuthenticator class]];
if(!doSmtpAuth && userId > 0)
{
doSmtpAuth = [currentAcount objectForKey: @"smtpAuth"] ? [[currentAcount objectForKey: @"smtpAuth"] boolValue] : NO;
@ -304,6 +307,7 @@
{
login = [currentAcount objectForKey: @"userName"];
password = [currentAcount objectForKey: @"password"];
smtpAuthMethod = "plain"; //Only support plain for auxiliary account
}
else
{
@ -322,8 +326,9 @@
if (isSystemMessage
&& ![[[SOGoUserManager sharedUserManager] getEmailForUID: [[authenticator userInContext: woContext] loginInDomain]] isEqualToString: sender]
&& smtpMasterUserEnabled) {
if (![client plainAuthenticateUser: smtpMasterUserUsername
withPassword: smtpMasterUserPassword]) {
if (![client authenticateUser: smtpMasterUserUsername
withPassword: smtpMasterUserPassword
withMethod: smtpAuthMethod]) {
result = [NSException exceptionWithHTTPStatus: 500
reason: @"cannot send message:"
@" (smtp) authentication failure"];
@ -334,8 +339,9 @@
{
if ([login length] == 0
|| [login isEqualToString: @"anonymous"]
|| ![client plainAuthenticateUser: login
withPassword: password])
|| ![client authenticateUser: login
withPassword: password
withMethod: smtpAuthMethod])
result = [NSException exceptionWithHTTPStatus: 500
reason: @"cannot send message:"
@" (smtp) authentication failure"];

View File

@ -0,0 +1,95 @@
/* SOGoCASSession.h - this file is part of SOGo
*
* Copyright (C) 2000-2023 Alinto.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef SOGOOPENIDSESSION_H
#define SOGOOPENIDSESSION_H
/* implementation of the OpenId Connect as required for a client/proxy:
https://openid.net/developers/how-connect-works/ */
#import <NGObjWeb/WOResponse.h>
@class NSString;
@class NSMutableDictionary;
@class NSURL;
@class NSJSONSerialization;
@interface SOGoOpenIdSession : NSObject
{
//For cache
BOOL cacheUpdateNeeded;
int userTokenInterval;
//From sogo.conf
BOOL openIdSessionIsOK;
NSString *openIdConfigUrl;
NSString *openIdScope;
NSString *openIdClient;
NSString *openIdClientSecret;
NSString *openIdEmailParam;
BOOL openIdEnableRefreshToken;
//From request to well-known/configuration
NSString *authorizationEndpoint;
NSString *tokenEndpoint;
NSString *introspectionEndpoint;
NSString *userinfoEndpoint;
NSString *endSessionEndpoint;
NSString *revocationEndpoint;
//Access token
NSString *accessToken;
NSString *refreshToken;
NSString *idToken;
NSString *tokenType;
NSNumber *expiresIn;
NSNumber *refreshExpiresIn;
}
+ (BOOL) checkUserConfig;
+ (SOGoOpenIdSession *) OpenIdSession;
+ (void) deleteValueForSessionKey: (NSString *) theSessionKey;
- (void) initialize;
- (BOOL) sessionIsOK;
- (WOResponse *) _performOpenIdRequest: (NSString *) endpoint
method: (NSString *) method
headers: (NSDictionary *) headers
body: (NSData *) body;
- (NSMutableDictionary *) fecthConfiguration;
- (void) setAccessToken;
- (NSString *) getRefreshToken;
- (NSString *) getToken;
- (NSString *) getCurrentToken;
- (NSString *) loginUrl: (NSString *) oldLocation;
- (NSMutableDictionary *) fetchToken: (NSString *) code redirect: (NSString *) oldLocation;
- (NSString *) logoutUrl;
- (NSMutableDictionary *) fetchUserInfo;
- (NSString *) _login;
- (NSString *) login: (NSString *) identifier;
@end
#endif /* SOGOOPENIDSESSION_H */

View File

@ -0,0 +1,663 @@
/* SOGoCASSession.m - this file is part of SOGo
*
* Copyright (C) 2010-2014 Inverse inc.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; see the file COPYING. If not, write to
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#import <NGObjWeb/WOHTTPConnection.h>
#import <NGObjWeb/WORequest.h>
#import <NGObjWeb/WOResponse.h>
#import <NGExtensions/NSObject+Logs.h>
#import <GDLContentStore/GCSOpenIdFolder.h>
#import <GDLContentStore/GCSFolderManager.h>
#import "NSDictionary+Utilities.h"
#import "NSString+Utilities.h"
#import "SOGoCache.h"
#import "SOGoSystemDefaults.h"
#import "SOGoOpenIdSession.h"
static BOOL SOGoOpenIDDebugEnabled = YES;
@implementation SOGoOpenIdSession
/// Check if all required parameters to set up a OpenIdSession are in sogo.conf
+ (BOOL) checkUserConfig
{
SOGoSystemDefaults *sd;
if(nil == [[GCSFolderManager defaultFolderManager] openIdFolder])
{
[self errorWithFormat: @"Something wrong with the table defined by OCSOpenIdURL"];
return NO;
}
sd = [SOGoSystemDefaults sharedSystemDefaults];
return ([sd openIdConfigUrl] && [sd openIdScope] && [sd openIdClient] && [sd openIdClientSecret]);
}
- (void) initialize
{
SOGoSystemDefaults *sd;
// //From sogo.conf
// openIdConfigUrl = nil;
// openIdScope = nil;
// openIdClient = nil;
// openIdClientSecret = nil;
// //From request to well-known/configuration
// //SHoud be ste in sogo.cong in case of oauth
// authorizationEndpoint = nil;
// tokenEndpoint = nil;
// introspectionEndpoint = nil;
// userinfoEndpoint = nil;
// endSessionEndpoint = nil;
// revocationEndpoint = nil;
// //Access token
// accessToken = nil;
sd = [SOGoSystemDefaults sharedSystemDefaults];
SOGoOpenIDDebugEnabled = [sd openIdDebugEnabled];
openIdSessionIsOK = NO;
if ([[self class] checkUserConfig])
{
openIdConfigUrl = [sd openIdConfigUrl];
openIdScope = [sd openIdScope];
openIdClient = [sd openIdClient];
openIdClientSecret = [sd openIdClientSecret];
openIdEmailParam = [sd openIdEmailParam];
openIdEnableRefreshToken = [sd openIdEnableRefreshToken];
userTokenInterval = [sd openIdTokenCheckInterval];
[self _loadSessionFromCache];
if(cacheUpdateNeeded)
{
[self fecthConfiguration];
}
}
else
{
[self errorWithFormat: @"Missing parameters from sogo.conf"];
}
}
- (WOResponse *) _performOpenIdRequest: (NSString *) endpoint
method: (NSString *) method
headers: (NSDictionary *) headers
body: (NSData *) body
{
NSURL *url;
NSUInteger status;
WORequest *request;
WOResponse *response;
WOHTTPConnection *httpConnection;
url = [NSURL URLWithString: endpoint];
if (url)
{
if(SOGoOpenIDDebugEnabled)
{
NSLog(@"OpenId perform request: %@ %@", method, [endpoint hostlessURL]);
NSLog(@"OpenId perform request, headers %@", headers);
if(body)
NSLog(@"OpenId perform request: content %@", [[NSString alloc] initWithData:body encoding:NSUTF8StringEncoding]);
}
httpConnection = [[WOHTTPConnection alloc] initWithURL: url];
[httpConnection autorelease];
request = [[WORequest alloc] initWithMethod: method
uri: [endpoint hostlessURL]
httpVersion: @"HTTP/1.1"
headers: headers content: body
userInfo: nil];
[request autorelease];
[httpConnection sendRequest: request];
response = [httpConnection readResponse];
status = [response status];
if(status >= 200 && status <500 && status != 404)
return response;
else if (status == 404)
{
[self errorWithFormat: @"OpenID endpoint not found (404): %@", endpoint];
return nil;
}
else
{
[self errorWithFormat: @"OpenID server internal error during %@: %@", endpoint, response];
return nil;
}
}
else
{
[self errorWithFormat: @"OpenID can't handle endpoint (not a url): '%@'", endpoint];
return nil;
}
}
- (NSMutableDictionary *) fecthConfiguration
{
NSString *location, *content;
WOResponse * response;
NSUInteger status;
NSMutableDictionary *result;
NSDictionary *config;
NSURL *url;
result = [NSMutableDictionary dictionary];
[result setObject: self->openIdConfigUrl forKey: @"url"];
url = [NSURL URLWithString: self->openIdConfigUrl ];
if (url)
{
response = [self _performOpenIdRequest: self->openIdConfigUrl
method: @"GET"
headers: nil
body: nil];
if (response)
{
status = [response status];
if(status >= 200 && status <300)
{
content = [response contentString];
config = [content objectFromJSONString];
self->authorizationEndpoint = [config objectForKey: @"authorization_endpoint"];
self->tokenEndpoint = [config objectForKey: @"token_endpoint"];
self->introspectionEndpoint = [config objectForKey: @"introspection_endpoint"];
self->userinfoEndpoint = [config objectForKey: @"userinfo_endpoint"];
self->endSessionEndpoint = [config objectForKey: @"end_session_endpoint"];
self->revocationEndpoint = [config objectForKey: @"revocation_endpoint"];
openIdSessionIsOK = YES;
[self _saveSessionToCache];
}
else
{
[self logWithFormat: @"Error during fetching the configuration (status %d), response: %@", status, response];
}
}
else
[result setObject: @"http-error" forKey: @"error"];
}
else
[result setObject: @"invalid-url" forKey: @"error"];
return result;
}
+ (SOGoOpenIdSession *) OpenIdSession
{
SOGoOpenIdSession *newSession;
newSession = [self new];
[newSession autorelease];
[newSession initialize];
return newSession;
}
+ (SOGoOpenIdSession *) OpenIdSessionWithToken: (NSString *) token
{
SOGoOpenIdSession *newSession;
if (token)
{
newSession = [self new];
[newSession autorelease];
[newSession initialize];
[newSession setAccessToken: token];
}
else
newSession = nil;
return newSession;
}
- (BOOL) sessionIsOk
{
return self->openIdSessionIsOK;
}
- (void) _loadSessionFromCache
{
SOGoCache *cache;
NSString *jsonSession;
NSDictionary *sessionDict;
cache = [SOGoCache sharedCache];
jsonSession = [cache openIdSessionFromServer: self->openIdConfigUrl];
if ([jsonSession length])
{
sessionDict = [jsonSession objectFromJSONString];
ASSIGN (authorizationEndpoint, [sessionDict objectForKey: @"authorization_endpoint"]);
ASSIGN (tokenEndpoint, [sessionDict objectForKey: @"token_endpoint"]);
ASSIGN (introspectionEndpoint, [sessionDict objectForKey: @"introspection_endpoint"]);
ASSIGN (userinfoEndpoint, [sessionDict objectForKey: @"userinfo_endpoint"]);
ASSIGN (endSessionEndpoint, [sessionDict objectForKey: @"end_session_endpoint"]);
ASSIGN (revocationEndpoint, [sessionDict objectForKey: @"revocation_endpoint"]);
openIdSessionIsOK = YES;
}
else
cacheUpdateNeeded = YES;
}
- (void) _saveSessionToCache
{
SOGoCache *cache;
NSString *jsonSession;
NSMutableDictionary *sessionDict;
cache = [SOGoCache sharedCache];
sessionDict = [NSMutableDictionary dictionary];
[sessionDict setObject: authorizationEndpoint forKey: @"authorization_endpoint"];
[sessionDict setObject: tokenEndpoint forKey: @"token_endpoint"];
[sessionDict setObject: introspectionEndpoint forKey: @"introspection_endpoint"];
[sessionDict setObject: userinfoEndpoint forKey: @"userinfo_endpoint"];
[sessionDict setObject: endSessionEndpoint forKey: @"end_session_endpoint"];
[sessionDict setObject: revocationEndpoint forKey: @"revocation_endpoint"];
jsonSession = [sessionDict jsonRepresentation];
[cache setOpenIdSession: jsonSession
forServer: self->openIdConfigUrl];
}
- (BOOL) _loadUserFromCache: (NSString *) email
{
/*
Return NO if the user token must be check again, YES the user is valid for now
*/
SOGoCache *cache;
NSString *identifier, *nextCheck;
NSTimeInterval nextCheckTime, timeNow;
identifier = [self->openIdClient stringByAppendingString: email];
cache = [SOGoCache sharedCache];
nextCheck = [cache userOpendIdSessionFromServer: identifier];
if ([nextCheck length])
{
timeNow = [[NSDate date] timeIntervalSince1970];
nextCheckTime = [nextCheck doubleValue];
if(timeNow > nextCheckTime)
return NO;
else
return YES;
}
else
return NO;
}
- (void) _saveUserToCache: (NSString *) email
{
SOGoCache *cache;
NSString *identifier, *nextCheck;
NSTimeInterval nextCheckTime;
identifier = [self->openIdClient stringByAppendingString: email];
nextCheckTime = [[NSDate date] timeIntervalSince1970] + self->userTokenInterval;
nextCheck = [NSString stringWithFormat:@"%f", nextCheckTime];
cache = [SOGoCache sharedCache];
[cache setUserOpendIdSessionFromServer: identifier
nextCheckAfter: nextCheck];
}
- (NSString*) loginUrl: (NSString *) oldLocation
{
NSString* logUrl;
logUrl = [self->authorizationEndpoint stringByAppendingFormat: @"?scope=%@", self->openIdScope];
logUrl = [logUrl stringByAppendingString: @"&response_type=code"];
logUrl = [logUrl stringByAppendingFormat: @"&client_id=%@", self->openIdClient];
logUrl = [logUrl stringByAppendingFormat: @"&redirect_uri=%@", oldLocation];
// logurl = [self->logurl stringByAppendingFormat: @"&state=%@", state];
return logUrl;
}
- (NSString *) logoutUrl
{
return self->endSessionEndpoint;
}
- (NSString*) getRefreshToken
{
return self->refreshToken;
}
- (NSString*) getToken
{
return self->accessToken;
}
- (void) setAccessToken: (NSString* ) token
{
self->accessToken = token;
}
- (NSString *) getCurrentToken
{
NSString *currentToken;
//See if the current token has been refreshed
currentToken = [[[GCSFolderManager defaultFolderManager] openIdFolder] getNewToken: self->accessToken];
if(currentToken)
{
//be sure the old token has been cleaned
[[[GCSFolderManager defaultFolderManager] openIdFolder] deleteOpenIdSessionFor: self->accessToken];
return currentToken;
}
return self->accessToken;
}
- (NSMutableDictionary *) fetchToken: (NSString * ) code redirect: (NSString *) oldLocation
{
NSString *location, *form, *content;
WOResponse *response;
NSUInteger status;
NSMutableDictionary *result;
NSDictionary *headers;
NSDictionary *tokenRet;
NSURL *url;
result = [NSMutableDictionary dictionary];
[result setObject: @"ok" forKey: @"error"];
location = self->tokenEndpoint;
url = [NSURL URLWithString: location];
if (url)
{
form = @"grant_type=authorization_code";
form = [form stringByAppendingFormat: @"&code=%@", code];
form = [form stringByAppendingFormat: @"&redirect_uri=%@", [oldLocation stringByEscapingURL]];
form = [form stringByAppendingFormat: @"&client_secret=%@", self->openIdClientSecret];
form = [form stringByAppendingFormat: @"&client_id=%@", self->openIdClient];
headers = [NSDictionary dictionaryWithObject: @"application/x-www-form-urlencoded" forKey: @"content-type"];
response = [self _performOpenIdRequest: location
method: @"POST"
headers: headers
body: [form dataUsingEncoding:NSUTF8StringEncoding]];
if (response)
{
status = [response status];
if(status >= 200 && status <300)
{
content = [response contentString];
tokenRet = [content objectFromJSONString];
if (SOGoOpenIDDebugEnabled)
{
NSLog(@"fetch token response: %@", tokenRet);
}
self->accessToken = [tokenRet objectForKey: @"access_token"];
self->refreshToken = [tokenRet objectForKey: @"refresh_token"];
self->refreshExpiresIn = [tokenRet objectForKey: @"refresh_expires_in"];
self->idToken = [tokenRet objectForKey: @"id_token"];
self->tokenType = [tokenRet objectForKey: @"token_type"];
self->expiresIn = [tokenRet objectForKey: @"expires_in"];
}
else
{
[self logWithFormat: @"Error during fetching the token (status %d), response: %@", status, response];
}
}
else
{
[result setObject: @"http-error" forKey: @"error"];
}
}
else
[result setObject: @"invalid-url" forKey: @"error"];
return result;
}
- (NSMutableDictionary *) refreshToken: (NSString * ) userRefreshToken
{
NSString *location, *form, *content;
WOResponse *response;
NSUInteger status;
NSMutableDictionary *result;
NSDictionary *headers;
NSDictionary *refreshTokenRet;
NSURL *url;
result = [NSMutableDictionary dictionary];
[result setObject: @"ok" forKey: @"error"];
if(!userRefreshToken || [userRefreshToken length] == 0)
{
[result setObject: @"invalid-token" forKey: @"error"];
return result;
}
location = self->tokenEndpoint;
url = [NSURL URLWithString: location];
if (url)
{
form = @"grant_type=refresh_token";
form = [form stringByAppendingFormat: @"&scope=%@", self->openIdScope];
form = [form stringByAppendingFormat: @"&refresh_token=%@", userRefreshToken];
form = [form stringByAppendingFormat: @"&client_secret=%@", self->openIdClientSecret];
form = [form stringByAppendingFormat: @"&client_id=%@", self->openIdClient];
headers = [NSDictionary dictionaryWithObject: @"application/x-www-form-urlencoded" forKey: @"content-type"];
response = [self _performOpenIdRequest: location
method: @"POST"
headers: headers
body: [form dataUsingEncoding:NSUTF8StringEncoding]];
if (response)
{
status = [response status];
if(status >= 200 && status <300)
{
content = [response contentString];
refreshTokenRet = [content objectFromJSONString];
if (SOGoOpenIDDebugEnabled)
{
NSLog(@"refresh token response: %@", refreshTokenRet);
}
self->accessToken = [refreshTokenRet objectForKey: @"access_token"];
self->refreshToken = [refreshTokenRet objectForKey: @"refresh_token"];
self->refreshExpiresIn = [refreshTokenRet objectForKey: @"refresh_expires_in"];
self->tokenType = [refreshTokenRet objectForKey: @"token_type"];
self->expiresIn = [refreshTokenRet objectForKey: @"expires_in"];
}
else
{
[self logWithFormat: @"Error during refreshing the token (status %d), response: %@", status, response];
}
}
else
{
[result setObject: @"http-error" forKey: @"error"];
}
}
else
[result setObject: @"invalid-url" forKey: @"error"];
return result;
}
- (NSMutableDictionary *) fetchUserInfo
{
NSString *location, *auth, *content;
WOResponse *response;
NSUInteger status;
NSMutableDictionary *result;
NSDictionary *profile, *headers;
NSURL *url;
NSString *email;
result = [NSMutableDictionary dictionary];
[result setObject: @"ok" forKey: @"error"];
location = self->userinfoEndpoint;
url = [NSURL URLWithString: location];
if (url)
{
auth = [NSString stringWithFormat: @"Bearer %@", self->accessToken];
headers = [NSDictionary dictionaryWithObject: auth forKey: @"authorization"];
response = [self _performOpenIdRequest: location
method: @"GET"
headers: headers
body: nil];
if (response)
{
status = [response status];
if(status >= 200 && status <300)
{
content = [response contentString];
profile = [content objectFromJSONString];
if(SOGoOpenIDDebugEnabled && profile)
NSLog(@"OpenId fetch user info, profile is %@", profile);
/*profile = {"sub":"70a3e6a1-37cf-4cf6-b114-6973aabca86a",
"email_verified":false,
"address":{},
"name":"Foo Bar",
"preferred_username":"myuser",
"given_name":"Foo",
"family_name":"Bar",
"email":"myuser@user.com"}*/
if (email = [profile objectForKey: self->openIdEmailParam])
{
if(self->userTokenInterval > 0)
[self _saveUserToCache: email];
[result setObject: email forKey: @"login"];
}
else
[result setObject: @"no mail found" forKey: @"error"];
}
else
{
[self logWithFormat: @"Error during fetching the token (status %d), response: %@", status, response];
[result setObject: @"http-error" forKey: @"error"];
}
}
else
{
[result setObject: @"http-error" forKey: @"error"];
}
}
else
[result setObject: @"invalid-url" forKey: @"error"];
return result;
}
+ (void) deleteValueForSessionKey: (NSString *) theSessionKey
{
GCSOpenIdFolder *folder;
folder = [[GCSFolderManager defaultFolderManager] openIdFolder];
[folder deleteOpenIdSessionFor: theSessionKey];
}
- (NSString *) _login
{
NSMutableDictionary *resultUserInfo, *resultResfreh;
NSString* _oldAccessToken;
resultUserInfo = [self fetchUserInfo];
if ([[resultUserInfo objectForKey: @"error"] isEqualToString: @"ok"])
{
//Save some infos as the refresh token to the database
[[[GCSFolderManager defaultFolderManager] openIdFolder] writeOpenIdSession: self->accessToken
withOldSession: nil
withRefreshToken: self->refreshToken
withExpire: self->expiresIn
withRefreshExpire: self->refreshExpiresIn];
return [resultUserInfo objectForKey: @"login"];
}
else if(openIdEnableRefreshToken)
{
//try to refresh
if(self->accessToken)
{
self->refreshToken = [[[GCSFolderManager defaultFolderManager] openIdFolder] getRefreshToken: self->accessToken];
//remove old session
[[[GCSFolderManager defaultFolderManager] openIdFolder] deleteOpenIdSessionFor: self->accessToken];
}
if(self->refreshToken)
{
_oldAccessToken = self->accessToken;
resultResfreh = [self refreshToken: self->refreshToken];
if ([[resultResfreh objectForKey: @"error"] isEqualToString: @"ok"])
{
resultUserInfo = [self fetchUserInfo];
if ([[resultUserInfo objectForKey: @"error"] isEqualToString: @"ok"])
{
[[[GCSFolderManager defaultFolderManager] openIdFolder] writeOpenIdSession: self->accessToken
withOldSession: _oldAccessToken
withRefreshToken: self->refreshToken
withExpire: self->expiresIn
withRefreshExpire: self->refreshExpiresIn];
return [resultUserInfo objectForKey: @"login"];
}
}
}
//The acces token hasn't work, delete the session in database if needed
if(self->accessToken)
{
[[[GCSFolderManager defaultFolderManager] openIdFolder] deleteOpenIdSessionFor: self->accessToken];
}
[self errorWithFormat: @"Can't get user email from profile because: %@", [resultUserInfo objectForKey: @"error"]];
}
else
{
//remove old session
[[[GCSFolderManager defaultFolderManager] openIdFolder] deleteOpenIdSessionFor: self->accessToken];
}
return @"anonymous";
}
- (BOOL) login: (NSString *) email
{
//Check if we need to fetch userinfo
if(self->userTokenInterval > 0 && [self _loadUserFromCache: email])
{
return email;
}
else
{
return [self _login];
}
}
@end

View File

@ -77,6 +77,7 @@ static const NSString *kDisableSharingCalendar = @"Calendar";
- (BOOL) uixDebugEnabled;
- (BOOL) easDebugEnabled;
- (BOOL) openIdDebugEnabled;
- (BOOL) tnefDecoderDebugEnabled;
- (BOOL) xsrfValidationEnabled;
@ -89,11 +90,21 @@ NSComparisonResult languageSort(id el1, id el2, void *context);
- (NSString *) loginSuffix;
- (NSString *) authenticationType;
- (BOOL) isSsoUsed;
- (NSString *) davAuthenticationType;
- (NSString *) CASServiceURL;
- (BOOL) CASLogoutEnabled;
- (NSString *) openIdConfigUrl;
- (NSString *) openIdScope;
- (NSString *) openIdClient;
- (NSString *) openIdClientSecret;
- (NSString *) openIdEmailParam;
- (BOOL) openIdEnableRefreshToken;
- (BOOL) openIdLogoutEnabled;
- (int) openIdTokenCheckInterval;
- (NSString *) SAML2PrivateKeyLocation;
- (NSString *) SAML2CertificateLocation;
- (NSString *) SAML2IdpMetadataLocation;

View File

@ -512,6 +512,11 @@ _injectConfigurationFromFile (NSMutableDictionary *defaultsDict,
return [self boolForKey: @"SOGoEASDebugEnabled"];
}
- (BOOL) openIdDebugEnabled
{
return [self boolForKey: @"SOGoOpenIDDebugEnabled"];
}
- (BOOL) tnefDecoderDebugEnabled
{
return [self boolForKey: @"SOGoTnefDecoderDebugEnabled"];
@ -582,6 +587,14 @@ NSComparisonResult languageSort(id el1, id el2, void *context)
return [[self stringForKey: @"SOGoAuthenticationType"] lowercaseString];
}
- (BOOL) isSsoUsed
{
NSString* authType;
authType = [self authenticationType];
return ([authType isEqualToString: @"cas"] || [authType isEqualToString: @"saml2"] || [authType isEqualToString: @"openid"]);
}
- (NSString *) davAuthenticationType
{
return [[self stringForKey: @"SOGoDAVAuthenticationType"] lowercaseString];
@ -597,6 +610,61 @@ NSComparisonResult languageSort(id el1, id el2, void *context)
return [self boolForKey: @"SOGoCASLogoutEnabled"];
}
/* OpenId Support */
- (NSString *) openIdConfigUrl
{
return [self stringForKey: @"SOGoOpenIdConfigUrl"];
}
- (NSString *) openIdScope
{
return [self stringForKey: @"SOGoOpenIdScope"];
}
- (NSString *) openIdClient
{
return [self stringForKey: @"SOGoOpenIdClient"];
}
- (NSString *) openIdClientSecret
{
return [self stringForKey: @"SOGoOpenIdClientSecret"];
}
- (NSString *) openIdEmailParam
{
NSString *emailParam;
emailParam = [self stringForKey: @"SOGoOpenIdEmailParam"];
if(!emailParam)
emailParam = @"email";
return emailParam;
}
- (BOOL) openIdLogoutEnabled
{
return [self boolForKey: @"SOGoOpenIdLogoutEnabled"];
}
- (int) openIdTokenCheckInterval
{
int v;
v = [self integerForKey: @"SOGoOpenIdTokenCheckInterval"];
if (!v)
v = 0;
if(v<0)
v = 0;
return v;
}
- (BOOL) openIdEnableRefreshToken
{
return [self boolForKey: @"SOGoOpenIdEnableRefreshToken"];
}
/* SAML2 support */
- (NSString *) SAML2PrivateKeyLocation
{

View File

@ -58,6 +58,8 @@
andPassword: (NSString *) password
inContext: (WOContext *) context;
- (NSArray *)getCookiesIfNeeded: (WOContext *)_ctx;
@end
#endif /* _SOGOWEBAUTHENTICATOR_H_ */

View File

@ -35,6 +35,7 @@
#import "SOGoCache.h"
#import "SOGoCASSession.h"
#import "SOGoOpenIdSession.h"
#import "SOGoPermissions.h"
#import "SOGoSession.h"
#import "SOGoSystemDefaults.h"
@ -133,7 +134,8 @@
additionalInfo: (NSMutableDictionary **)_additionalInfo
useCache: (BOOL) _useCache
{
SOGoCASSession *session;
SOGoCASSession *casSession;
SOGoOpenIdSession * openIdSession;
SOGoSystemDefaults *sd;
NSString *authenticationType;
BOOL rc;
@ -143,12 +145,20 @@
authenticationType = [sd authenticationType];
if ([authenticationType isEqualToString: @"cas"])
{
session = [SOGoCASSession CASSessionWithIdentifier: _pwd fromProxy: NO];
if (session)
rc = [[session login] isEqualToString: _login];
casSession = [SOGoCASSession CASSessionWithIdentifier: _pwd fromProxy: NO];
if (casSession)
rc = [[casSession login] isEqualToString: _login];
else
rc = NO;
}
else if ([authenticationType isEqualToString: @"openid"])
{
openIdSession = [SOGoOpenIdSession OpenIdSessionWithToken: _pwd];
if (openIdSession)
rc = [[openIdSession login: _login] isEqualToString: _login];
else
rc = NO;
}
#if defined(SAML2_CONFIG)
else if ([authenticationType isEqualToString: @"saml2"])
{
@ -310,6 +320,15 @@
if ([password length] || renew)
[session updateCache];
}
else if ([authType isEqualToString: @"openid"])
{
SOGoOpenIdSession* session;
NSString* currentToken;
//If the token has been refresh during the request, we need to use the new access_token
//as the one from the cookie is no more valid
session = [SOGoOpenIdSession OpenIdSessionWithToken: password];
password = [session getCurrentToken];
}
#if defined(SAML2_CONFIG)
else if ([authType isEqualToString: @"saml2"])
{
@ -436,4 +455,42 @@
return authCookie;
}
- (NSArray *) getCookiesIfNeeded: (WOContext *)_ctx
{
NSArray *listCookies = nil;
SOGoSystemDefaults *sd;
NSString *authType;
sd = [SOGoSystemDefaults sharedSystemDefaults];
authType = [sd authenticationType];
if([authType isEqualToString:@"openid"] && [sd openIdEnableRefreshToken])
{
NSString *currentPassword, *newPassword, *username;
SOGoOpenIdSession *openIdSession;
WOCookie* newCookie;
currentPassword = [self passwordInContext: _ctx];
newPassword = [self imapPasswordInContext: _ctx forURL: nil forceRenew: NO];
if(currentPassword && newPassword && ![newPassword isEqualToString: currentPassword])
{
openIdSession = [SOGoOpenIdSession OpenIdSessionWithToken: newPassword];
if (openIdSession)
username = [openIdSession login: @""]; //Force to refresh the name
else
username = [[self userInContext: _ctx] login];
newCookie = [self cookieWithUsername: username
andPassword: newPassword
inContext: _ctx];
listCookies = [[NSArray alloc] initWithObjects: newCookie, nil];
[listCookies autorelease];
}
if(listCookies && [listCookies isKindOfClass:[NSArray class]] && [listCookies count] > 0)
return listCookies;
else
return nil;
}
else
return nil;
}
@end /* SOGoWebAuthenticator */

View File

@ -33,6 +33,7 @@
#import <SOGo/NSString+Utilities.h>
#import <SOGo/SOGoSession.h>
#import <SOGo/SOGoOpenIdSession.h>
#import "SOGoTool.h"
@ -59,6 +60,73 @@
"The expire-sessions action should be configured as a cronjob.\n");
}
- (BOOL) expireUserOpenIdSessionOlderThan: (int) nbMinutes
{
BOOL rc;
EOAdaptorChannel *channel;
GCSChannelManager *cm;
NSArray *attrs;
NSDictionary *qresult;
NSException *ex;
NSString *sql, *sessionsFolderURL, *sessionID;
NSURL *tableURL;
NSUserDefaults *ud;
unsigned int now, oldest;
rc = YES;
ud = [NSUserDefaults standardUserDefaults];
now = [[NSCalendarDate calendarDate] timeIntervalSince1970];
oldest = now - (nbMinutes * 60);
sessionID = nil;
sessionsFolderURL = [ud stringForKey: @"OCSOpenIdURL"];
if (!sessionsFolderURL)
{
if (verbose)
NSLog(@"Couldn't read OCSOpenIdURL");
return rc = NO;
}
tableURL = [[NSURL alloc] initWithString: sessionsFolderURL];
cm = [GCSChannelManager defaultChannelManager];
channel = [cm acquireOpenChannelForURL: tableURL];
if (!channel)
{
/* FIXME: nice error msg */
NSLog(@"Can't aquire channel");
return rc = NO;
}
sql = [NSString stringWithFormat: @"SELECT c_user_session FROM %@ WHERE c_access_token_expires_in <= %d",
[tableURL gcsTableName], oldest];
ex = [channel evaluateExpressionX: sql];
if (ex)
{
NSLog(@"%@", [ex reason]);
[ex raise];
return rc = NO;
}
attrs = [channel describeResults: NO];
while ((qresult = [channel fetchAttributes: attrs withZone: NULL]))
{
sessionID = [qresult objectForKey: @"c_user_session"];
if (sessionID)
{
if (verbose)
NSLog(@"Removing session %@", sessionID);
[SOGoOpenIdSession deleteValueForSessionKey: sessionID];
}
}
[cm releaseChannel: channel immediately: YES];
if (verbose && sessionID == nil)
NSLog(@"No session to remove on openId");
return rc;
}
- (BOOL) expireUserSessionOlderThan: (int) nbMinutes
{
BOOL rc;
@ -67,7 +135,7 @@
NSArray *attrs;
NSDictionary *qresult;
NSException *ex;
NSString *sql, *sessionsFolderURL, *sessionID;
NSString *sql, *sessionsFolderURL, *sessionID, *authType;
NSURL *tableURL;
NSUserDefaults *ud;
@ -123,6 +191,13 @@
if (verbose && sessionID == nil)
NSLog(@"No session to remove");
//doing openid session if needed
authType = [ud stringForKey:@"SOGoAuthenticationType"];
if([authType isEqualToString: @"openid"])
{
[self expireUserOpenIdSessionOlderThan: nbMinutes];
}
return rc;
}

View File

@ -497,6 +497,15 @@
return [[sd authenticationType] isEqualToString: @"cas"];
}
- (BOOL) usesOpenIdAuthentication
{
SOGoSystemDefaults *sd;
sd = [SOGoSystemDefaults sharedSystemDefaults];
return [[sd authenticationType] isEqualToString: @"openid"];
}
- (BOOL) usesSAML2Authenticationx
{
SOGoSystemDefaults *sd;
@ -545,11 +554,13 @@
sd = [SOGoSystemDefaults sharedSystemDefaults];
authType = [sd authenticationType];
if ([authType isEqualToString: @"cas"])
canLogoff = [sd CASLogoutEnabled];
canLogoff = [sd CASLogoutEnabled];
else if ([authType isEqualToString: @"saml2"])
canLogoff = [sd SAML2LogoutEnabled];
canLogoff = [sd SAML2LogoutEnabled];
else if ([authType isEqualToString: @"openid"])
canLogoff = [sd openIdLogoutEnabled];
else
canLogoff = [[auth cookieNameInContext: context] length] > 0;
canLogoff = [[auth cookieNameInContext: context] length] > 0;
}
else
canLogoff = NO;

View File

@ -40,6 +40,7 @@
#import <SOGo/SOGoBuild.h>
#import <SOGo/SOGoCache.h>
#import <SOGo/SOGoCASSession.h>
#import <SOGo/SOGoOpenIdSession.h>
#if defined(SAML2_CONFIG)
#import <SOGo/SOGoSAML2Session.h>
#endif /* SAML2_ENABLE */
@ -478,42 +479,42 @@ static const NSString *kJwtKey = @"jwt";
if ([login isEqualToString: @"anonymous"])
login = nil;
if (!login)
{
rq = [context request];
ticket = [rq formValueForKey: @"ticket"];
if ([ticket length])
{
rq = [context request];
ticket = [rq formValueForKey: @"ticket"];
if ([ticket length])
{
casSession = [SOGoCASSession CASSessionWithTicket: ticket
fromProxy: NO];
login = [casSession login];
if ([login length])
{
auth = [[WOApplication application]
authenticatorInContext: context];
casCookie = [auth cookieWithUsername: login
andPassword: [casSession identifier]
inContext: context];
[casSession updateCache];
newLocation = [rq cookieValueForKey: @"cas-location"];
/* login callback, we expire the "cas-location" cookie, created
below */
casLocationCookie = [self _authLocationCookie: YES
withName: @"cas-location"];
}
}
else
{
/* anonymous and no ticket, possibly a logout request from CAS
* See: https://wiki.jasig.org/display/CASUM/Single+Sign+Out
*/
logoutRequest = [rq formValueForKey: @"logoutRequest"];
if ([logoutRequest length])
{
[SOGoCASSession handleLogoutRequest: logoutRequest];
return [self responseWithStatus: 200];
}
}
casSession = [SOGoCASSession CASSessionWithTicket: ticket
fromProxy: NO];
login = [casSession login];
if ([login length])
{
auth = [[WOApplication application]
authenticatorInContext: context];
casCookie = [auth cookieWithUsername: login
andPassword: [casSession identifier]
inContext: context];
[casSession updateCache];
newLocation = [rq cookieValueForKey: @"cas-location"];
/* login callback, we expire the "cas-location" cookie, created
below */
casLocationCookie = [self _authLocationCookie: YES
withName: @"cas-location"];
}
}
else
{
/* anonymous and no ticket, possibly a logout request from CAS
* See: https://wiki.jasig.org/display/CASUM/Single+Sign+Out
*/
logoutRequest = [rq formValueForKey: @"logoutRequest"];
if ([logoutRequest length])
{
[SOGoCASSession handleLogoutRequest: logoutRequest];
return [self responseWithStatus: 200];
}
}
}
else
ticket = nil;
@ -548,6 +549,115 @@ static const NSString *kJwtKey = @"jwt";
return response;
}
- (id <WOActionResults>) _openidDefaultAction
{
WOResponse *response;
NSString *login, *redirectLocation, *serverUrl;
NSString *sessionState, *code, *refreshTokenValue;
NSURL *newLocation, *oldLocation;
NSDictionary *formValues;
SOGoUser *loggedInUser;
WOCookie *openIdCookie, *openIdCookieLocation, *openIdRefreshCookie;
WORequest *rq;
SOGoWebAuthenticator *auth;
SOGoOpenIdSession *openIdSession;
id value;
openIdCookie = nil;
openIdCookieLocation = nil;
openIdRefreshCookie = nil;
newLocation = nil;
openIdSession = [SOGoOpenIdSession OpenIdSession];
if(![openIdSession sessionIsOk])
{
return [NSException exceptionWithHTTPStatus: 502 /* Bad OpenId Configuration */
reason: [NSString stringWithFormat: @"OpenId server not found or has unexpected behavior, contact your admin."]];
}
login = [[context activeUser] login];
rq = [context request];
if ([login isEqualToString: @"anonymous"])
login = nil;
if (!login)
{
//You get here if you nerver been logged in or if you token is expired
serverUrl = [[context serverURL] absoluteString];
redirectLocation = [NSString stringWithFormat: @"%@/%@/", serverUrl, [rq applicationName]];
NSLog(@"ServerUrl %@ and redirect: %@", serverUrl, redirectLocation);
if((formValues = [rq formValues]) && [formValues objectForKey: @"code"])
{
//You get here if this is the callback of openid after you logged in
//NOT MANDATORY
// value = [formValues objectForKey: @"session_state"];
// if ([value isKindOfClass: [NSArray class]])
// sessionState = [value lastObject];
// else
// sessionState = value;
value = [formValues objectForKey: @"code"];
if ([value isKindOfClass: [NSArray class]])
code = [value lastObject];
else
code = value;
[openIdSession fetchToken: code redirect: redirectLocation];
login = [openIdSession login: @""];
if ([login length])
{
auth = [[WOApplication application] authenticatorInContext: context];
openIdCookie = [auth cookieWithUsername: login
andPassword: [openIdSession getToken]
inContext: context];
}
newLocation = [rq cookieValueForKey: @"openid-location"];
openIdCookieLocation = [self _authLocationCookie: YES withName: @"openid-location"];
}
// else if((formValues = [rq formValues]) && [formValues objectForKey: @"action"])
// {
// value = [formValues objectForKey: @"action"];
// if ([value isKindOfClass: [NSArray class]])
// code = [value lastObject];
// else
// code = value;
// if([code isEqualToString:@"redirect"])
// {
// //this action onlye serve to make a redirection to openId server after a GET request
// newLocation = [openIdSession loginUrl: redirectLocation];
// }
//}
else
{
// //You get here the first time you access sogo, it redirect to last location
// if([[rq method] isEqualToString: @"POST"])
// //To avoid making a redirection to openid server after a post request, we first redirect to a get method
// newLocation = [NSString stringWithFormat: @"%@?action=redirect", redirectLocation];
// else
newLocation = [openIdSession loginUrl: redirectLocation];
openIdCookieLocation = [self _authLocationCookie: NO withName: @"openid-location"];
}
}
else
{
if (!newLocation)
{
oldLocation = [[self clientObject] baseURLInContext: context];
newLocation = [NSString stringWithFormat: @"%@%@", oldLocation, [login stringByEscapingURL]];
}
loggedInUser = [SOGoUser userWithLogin: login];
[self _checkAutoReloadWebCalendars: loggedInUser];
}
response = [self redirectToLocation: newLocation];
if (openIdCookie)
[response addCookie: openIdCookie];
if (openIdCookieLocation)
[response addCookie: openIdCookieLocation];
//[response setStatus: 303];
return response;
}
#if defined(SAML2_CONFIG)
- (id <WOActionResults>) _saml2DefaultAction
{
@ -615,16 +725,13 @@ static const NSString *kJwtKey = @"jwt";
[[SOGoUser getEncryptedUsernameIfNeeded:login request: [context request]] stringByEscapingURL]]];
}
else
{
oldLocation = [[context request] uri];
if ([context clientObject]
&& ![oldLocation hasSuffix: @"/"]
&& ![oldLocation hasSuffix: @"/view"])
response = [self redirectToLocation:
[NSString stringWithFormat: @"%@/", oldLocation]];
else
response = self;
}
{
oldLocation = [[context request] uri];
if ([context clientObject] && ![oldLocation hasSuffix: @"/"] && ![oldLocation hasSuffix: @"/view"])
response = [self redirectToLocation: [NSString stringWithFormat: @"%@/", oldLocation]];
else
response = self;
}
return response;
}
@ -638,6 +745,8 @@ static const NSString *kJwtKey = @"jwt";
authenticationType];
if ([authenticationType isEqualToString: @"cas"])
result = [self _casDefaultAction];
else if ([authenticationType isEqualToString: @"openid"])
result = [self _openidDefaultAction];
#if defined(SAML2_CONFIG)
else if ([authenticationType isEqualToString: @"saml2"])
result = [self _saml2DefaultAction];

View File

@ -32,6 +32,7 @@
#import <SOGo/SOGoCache.h>
#import <SOGo/SOGoCASSession.h>
#import <SOGo/SOGoOpenIdSession.h>
#if defined(SAML2_CONFIG)
#import <SOGo/SOGoSAML2Session.h>
#endif
@ -429,6 +430,14 @@
redirectURL = [SOGoCASSession CASURLWithAction: @"logout"
andParameters: nil];
}
else if ([[sd authenticationType] isEqualToString: @"openid"])
{
SOGoOpenIdSession* session;
session = [SOGoOpenIdSession OpenIdSession];
redirectURL = [session logoutUrl];
//delete openid session in database
}
#if defined(SAML2_CONFIG)
else if ([[sd authenticationType] isEqualToString: @"saml2"])
{

View File

@ -422,7 +422,7 @@ const punycode = {
* @memberOf punycode
* @type String
*/
'version': '2.3.1',
'version': '2.1.0',
/**
* An object of methods to convert from JavaScript's internal character
* representation (UCS-2) to Unicode code points, and back.