diff --git a/Documentation/SOGoInstallationGuide.asciidoc b/Documentation/SOGoInstallationGuide.asciidoc index 1474d09ce..fb85f25e0 100644 --- a/Documentation/SOGoInstallationGuide.asciidoc +++ b/Documentation/SOGoInstallationGuide.asciidoc @@ -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. diff --git a/Main/SOGo.m b/Main/SOGo.m index a0f93f2cc..5aff3ccd3 100644 --- a/Main/SOGo.m +++ b/Main/SOGo.m @@ -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; diff --git a/SOPE/GDLContentStore/GCSFolderManager.h b/SOPE/GDLContentStore/GCSFolderManager.h index 306d16635..d6d59802d 100644 --- a/SOPE/GDLContentStore/GCSFolderManager.h +++ b/SOPE/GDLContentStore/GCSFolderManager.h @@ -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; diff --git a/SOPE/GDLContentStore/GCSFolderManager.m b/SOPE/GDLContentStore/GCSFolderManager.m index dc48aa2c8..616a67350 100644 --- a/SOPE/GDLContentStore/GCSFolderManager.m +++ b/SOPE/GDLContentStore/GCSFolderManager.m @@ -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 { diff --git a/SOPE/GDLContentStore/GCSOpenIdFolder.h b/SOPE/GDLContentStore/GCSOpenIdFolder.h new file mode 100644 index 000000000..f6bd2965c --- /dev/null +++ b/SOPE/GDLContentStore/GCSOpenIdFolder.h @@ -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 */ diff --git a/SOPE/GDLContentStore/GCSOpenIdFolder.m b/SOPE/GDLContentStore/GCSOpenIdFolder.m new file mode 100644 index 000000000..dc6e90b64 --- /dev/null +++ b/SOPE/GDLContentStore/GCSOpenIdFolder.m @@ -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 +#import + +#import +#import +#import +#import + +#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 diff --git a/SOPE/GDLContentStore/GCSSpecialQueries.h b/SOPE/GDLContentStore/GCSSpecialQueries.h index d4f6e3272..224a2dcf3 100644 --- a/SOPE/GDLContentStore/GCSSpecialQueries.h +++ b/SOPE/GDLContentStore/GCSSpecialQueries.h @@ -42,6 +42,9 @@ - (NSString *)createAdminFolderWithName:(NSString *)tableName; - (NSDictionary *)adminAttributeTypes; +- (NSString *)createOpenIdFolderWithName:(NSString *)tableName; +- (NSDictionary *)openIdAttributeTypes; + - (NSString *) updateCPathInFolderInfo:(NSString *)tableName withCPath2:(NSString *)c_path2; diff --git a/SOPE/GDLContentStore/GCSSpecialQueries.m b/SOPE/GDLContentStore/GCSSpecialQueries.m index 327302025..167b38559 100644 --- a/SOPE/GDLContentStore/GCSSpecialQueries.m +++ b/SOPE/GDLContentStore/GCSSpecialQueries.m @@ -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 diff --git a/SOPE/GDLContentStore/GNUmakefile b/SOPE/GDLContentStore/GNUmakefile index 37db13280..9e8604396 100644 --- a/SOPE/GDLContentStore/GNUmakefile +++ b/SOPE/GDLContentStore/GNUmakefile @@ -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 diff --git a/Scripts/mysql-utf8mb4.sql b/Scripts/mysql-utf8mb4.sql index 9edda59b1..38dd90ecb 100644 --- a/Scripts/mysql-utf8mb4.sql +++ b/Scripts/mysql-utf8mb4.sql @@ -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, diff --git a/SoObjects/Mailer/SOGoMailBaseObject.m b/SoObjects/Mailer/SOGoMailBaseObject.m index 8c9b4f215..59b6b72ad 100644 --- a/SoObjects/Mailer/SOGoMailBaseObject.m +++ b/SoObjects/Mailer/SOGoMailBaseObject.m @@ -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 && diff --git a/SoObjects/SOGo/GNUmakefile b/SoObjects/SOGo/GNUmakefile index ee13dcbc6..d661d8c2e 100644 --- a/SoObjects/SOGo/GNUmakefile +++ b/SoObjects/SOGo/GNUmakefile @@ -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 \ diff --git a/SoObjects/SOGo/GNUmakefile.preamble b/SoObjects/SOGo/GNUmakefile.preamble index 3441969ba..419c0d37a 100644 --- a/SoObjects/SOGo/GNUmakefile.preamble +++ b/SoObjects/SOGo/GNUmakefile.preamble @@ -17,6 +17,7 @@ SOGo_LIBRARIES_DEPEND_UPON += \ -Wl,--no-as-needed \ -L../../SOPE/NGCards/$(GNUSTEP_OBJ_DIR)/ \ -lmemcached \ + -lcurl \ -lGDLAccess \ -lNGObjWeb \ -lNGCards \ diff --git a/SoObjects/SOGo/SOGoCache.h b/SoObjects/SOGo/SOGoCache.h index 092b3591d..3e3bae9b4 100644 --- a/SoObjects/SOGo/SOGoCache.h +++ b/SoObjects/SOGo/SOGoCache.h @@ -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 // diff --git a/SoObjects/SOGo/SOGoCache.m b/SoObjects/SOGo/SOGoCache.m index 73169178d..f207d4db8 100644 --- a/SoObjects/SOGo/SOGoCache.m +++ b/SoObjects/SOGo/SOGoCache.m @@ -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 // diff --git a/SoObjects/SOGo/SOGoMailer.m b/SoObjects/SOGo/SOGoMailer.m index 9f302da5f..db92ff17c 100644 --- a/SoObjects/SOGo/SOGoMailer.m +++ b/SoObjects/SOGo/SOGoMailer.m @@ -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"]; diff --git a/SoObjects/SOGo/SOGoOpenIdSession.h b/SoObjects/SOGo/SOGoOpenIdSession.h new file mode 100644 index 000000000..34775c695 --- /dev/null +++ b/SoObjects/SOGo/SOGoOpenIdSession.h @@ -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 + + +@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 */ diff --git a/SoObjects/SOGo/SOGoOpenIdSession.m b/SoObjects/SOGo/SOGoOpenIdSession.m new file mode 100644 index 000000000..e86170c59 --- /dev/null +++ b/SoObjects/SOGo/SOGoOpenIdSession.m @@ -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 +#import +#import +#import + +#import +#import + +#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 diff --git a/SoObjects/SOGo/SOGoSystemDefaults.h b/SoObjects/SOGo/SOGoSystemDefaults.h index 055d40bae..e70d359b7 100644 --- a/SoObjects/SOGo/SOGoSystemDefaults.h +++ b/SoObjects/SOGo/SOGoSystemDefaults.h @@ -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; diff --git a/SoObjects/SOGo/SOGoSystemDefaults.m b/SoObjects/SOGo/SOGoSystemDefaults.m index c51ab37ed..80ff32246 100644 --- a/SoObjects/SOGo/SOGoSystemDefaults.m +++ b/SoObjects/SOGo/SOGoSystemDefaults.m @@ -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 { diff --git a/SoObjects/SOGo/SOGoWebAuthenticator.h b/SoObjects/SOGo/SOGoWebAuthenticator.h index be8ebd261..ac26e7898 100644 --- a/SoObjects/SOGo/SOGoWebAuthenticator.h +++ b/SoObjects/SOGo/SOGoWebAuthenticator.h @@ -58,6 +58,8 @@ andPassword: (NSString *) password inContext: (WOContext *) context; +- (NSArray *)getCookiesIfNeeded: (WOContext *)_ctx; + @end #endif /* _SOGOWEBAUTHENTICATOR_H_ */ diff --git a/SoObjects/SOGo/SOGoWebAuthenticator.m b/SoObjects/SOGo/SOGoWebAuthenticator.m index 5569b8837..d327bb061 100644 --- a/SoObjects/SOGo/SOGoWebAuthenticator.m +++ b/SoObjects/SOGo/SOGoWebAuthenticator.m @@ -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 */ diff --git a/Tools/SOGoToolExpireUserSessions.m b/Tools/SOGoToolExpireUserSessions.m index 9aabd1d6a..231b435c3 100644 --- a/Tools/SOGoToolExpireUserSessions.m +++ b/Tools/SOGoToolExpireUserSessions.m @@ -33,6 +33,7 @@ #import #import +#import #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; } diff --git a/UI/Common/UIxPageFrame.m b/UI/Common/UIxPageFrame.m index 6c7eab81e..56f584a6b 100644 --- a/UI/Common/UIxPageFrame.m +++ b/UI/Common/UIxPageFrame.m @@ -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; diff --git a/UI/MainUI/SOGoRootPage.m b/UI/MainUI/SOGoRootPage.m index 276fb85e7..ccf1bc20d 100644 --- a/UI/MainUI/SOGoRootPage.m +++ b/UI/MainUI/SOGoRootPage.m @@ -40,6 +40,7 @@ #import #import #import +#import #if defined(SAML2_CONFIG) #import #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 ) _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 ) _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]; diff --git a/UI/MainUI/SOGoUserHomePage.m b/UI/MainUI/SOGoUserHomePage.m index 2fbbf83dc..df950b84f 100644 --- a/UI/MainUI/SOGoUserHomePage.m +++ b/UI/MainUI/SOGoUserHomePage.m @@ -32,6 +32,7 @@ #import #import +#import #if defined(SAML2_CONFIG) #import #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"]) { diff --git a/UI/WebServerResources/js/vendor/punycode.js b/UI/WebServerResources/js/vendor/punycode.js index 5b81f1763..8fdd43303 100644 --- a/UI/WebServerResources/js/vendor/punycode.js +++ b/UI/WebServerResources/js/vendor/punycode.js @@ -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.