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

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