mirror of
https://github.com/inverse-inc/sogo.git
synced 2026-02-17 07:33:57 +00:00
feat(openID): first stable version
This commit is contained in:
@@ -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 &&
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -17,6 +17,7 @@ SOGo_LIBRARIES_DEPEND_UPON += \
|
||||
-Wl,--no-as-needed \
|
||||
-L../../SOPE/NGCards/$(GNUSTEP_OBJ_DIR)/ \
|
||||
-lmemcached \
|
||||
-lcurl \
|
||||
-lGDLAccess \
|
||||
-lNGObjWeb \
|
||||
-lNGCards \
|
||||
|
||||
@@ -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
|
||||
//
|
||||
|
||||
@@ -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
|
||||
//
|
||||
|
||||
@@ -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"];
|
||||
|
||||
95
SoObjects/SOGo/SOGoOpenIdSession.h
Normal file
95
SoObjects/SOGo/SOGoOpenIdSession.h
Normal 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 */
|
||||
663
SoObjects/SOGo/SOGoOpenIdSession.m
Normal file
663
SoObjects/SOGo/SOGoOpenIdSession.m
Normal 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
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
{
|
||||
|
||||
@@ -58,6 +58,8 @@
|
||||
andPassword: (NSString *) password
|
||||
inContext: (WOContext *) context;
|
||||
|
||||
- (NSArray *)getCookiesIfNeeded: (WOContext *)_ctx;
|
||||
|
||||
@end
|
||||
|
||||
#endif /* _SOGOWEBAUTHENTICATOR_H_ */
|
||||
|
||||
@@ -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 */
|
||||
|
||||
Reference in New Issue
Block a user