From 41ed498d6512b9cf2ef01654ea29be4c535d0c16 Mon Sep 17 00:00:00 2001 From: Jean Raby Date: Thu, 20 Jun 2013 14:32:14 -0400 Subject: [PATCH] Add support for CAS LogoutRequests Fixes #2346 This changes the serviceURL sent by SOGo to the CAS server: /SOGo/so/ -> /SOGo/so/index --- NEWS | 1 + SoObjects/SOGo/GNUmakefile | 3 +- SoObjects/SOGo/SOGoCASSession.h | 11 +++++ SoObjects/SOGo/SOGoCASSession.m | 75 ++++++++++++++++++++++++++++++++- SoObjects/SOGo/SOGoCache.h | 2 + SoObjects/SOGo/SOGoCache.m | 11 +++++ UI/MainUI/SOGoRootPage.m | 19 ++++++++- 7 files changed, 118 insertions(+), 4 deletions(-) diff --git a/NEWS b/NEWS index 40d9f9a1d..0aa1dd27e 100644 --- a/NEWS +++ b/NEWS @@ -20,6 +20,7 @@ Bug fixes - fixed public access when SOGoTrustProxyAuthentication is used (#2237) - fixed access right issues with import feature (#2294) - fixed IMAP ACL issue when SOGoForceExternalLoginWithEmail is used (#2313) + - fixed handling of CAS logoutRequest (#2346) - OpenChange stability fixes 2.0.5a (2013-04-17) diff --git a/SoObjects/SOGo/GNUmakefile b/SoObjects/SOGo/GNUmakefile index f38ce600a..43bce3e54 100644 --- a/SoObjects/SOGo/GNUmakefile +++ b/SoObjects/SOGo/GNUmakefile @@ -140,7 +140,8 @@ SOGo_OBJC_FILES = \ SOGo_RESOURCE_FILES = \ SOGoDefaults.plist \ - DAVReportMap.plist + DAVReportMap.plist \ + CASLogoutRequestMap.plist ifeq ($(saml2_config), yes) SOGo_HEADER_FILES += SOGoSAML2Session.h SOGoSAML2Exceptions.h diff --git a/SoObjects/SOGo/SOGoCASSession.h b/SoObjects/SOGo/SOGoCASSession.h index ee958b5a4..ade1cf0a5 100644 --- a/SoObjects/SOGo/SOGoCASSession.h +++ b/SoObjects/SOGo/SOGoCASSession.h @@ -51,6 +51,8 @@ + (SOGoCASSession *) CASSessionWithIdentifier: (NSString *) newIdentifier fromProxy: (BOOL) fromProxy; ++ (void) handleLogoutRequest: (NSString *) logoutRequest; + - (NSString *) identifier; - (NSString *) ticket; @@ -65,4 +67,13 @@ @end +@interface CASLogoutRequest : NSObject +{ + NSString *sessionIndex; +} + +- (NSString *) sessionIndex; + +@end + #endif /* SOGOCASSESSION_H */ diff --git a/SoObjects/SOGo/SOGoCASSession.m b/SoObjects/SOGo/SOGoCASSession.m index 9104dbe03..6ab230df2 100644 --- a/SoObjects/SOGo/SOGoCASSession.m +++ b/SoObjects/SOGo/SOGoCASSession.m @@ -20,6 +20,7 @@ * Boston, MA 02111-1307, USA. */ +#import #import #import @@ -35,6 +36,11 @@ #import #import +#import +#import +#import +#import + #import "NSDictionary+Utilities.h" #import "NSString+Utilities.h" #import "SOGoCache.h" @@ -106,6 +112,43 @@ return session; } ++ (void) handleLogoutRequest: (NSString *) logoutRequest +{ + CASLogoutRequest *rq; + SOGoCache *cache; + NSBundle *bundle; + NSDictionary *root; + NSString *mapFile, *sessionIndex; + + bundle = [NSBundle bundleForClass: [self class]]; + mapFile = [bundle pathForResource: @"CASLogoutRequestMap" ofType: @"plist"]; + if (![mapFile length]) + { + [self errorWithFormat: @"mapFile not found (CASLogoutRequest.plist)"]; + return; + } + + id parser = nil; + SaxObjectDecoder *sax = nil; + parser = [[SaxXMLReaderFactory standardXMLReaderFactory] + createXMLReaderForMimeType:@"text/xml"]; + + sax = [[SaxObjectDecoder alloc] initWithMappingAtPath: mapFile]; + [sax autorelease]; + [parser setContentHandler:sax]; + [parser setErrorHandler:sax]; + [parser parseFromSource: logoutRequest]; + + rq = [sax rootObject]; + sessionIndex = [rq sessionIndex]; + + if ([sessionIndex length]) + { + cache = [SOGoCache sharedCache]; + [cache removeCASSessionWithTicket: sessionIndex]; + } +} + - (id) init { if ((self = [super init])) @@ -381,7 +424,12 @@ NSString *serviceURL; soURL = [[WOApplication application] soURL]; - serviceURL = [soURL absoluteString]; + + /* appending 'index' to /SOGo/so/ so that callbacks made by the CAS server + * are not greeted with a 302 generated by sope. The CAS server doesn't + * follow redirects, and we'd end up losing the LogoutRequests + */ + serviceURL = [NSString stringWithFormat: @"%@index", [soURL absoluteString]]; params = [NSDictionary dictionaryWithObjectsAndKeys: ticket, @"ticket", serviceURL, @"service", @@ -466,3 +514,28 @@ } @end + +@implementation CASLogoutRequest + +- (id) init +{ + if ((self = [super init])) + { + sessionIndex = nil; + } + + return self; +} + +- (void) dealloc +{ + [sessionIndex release]; + [super dealloc]; +} + +- (NSString *) sessionIndex +{ + return sessionIndex; +} + +@end diff --git a/SoObjects/SOGo/SOGoCache.h b/SoObjects/SOGo/SOGoCache.h index c8f1f9aa7..af6be9787 100644 --- a/SoObjects/SOGo/SOGoCache.h +++ b/SoObjects/SOGo/SOGoCache.h @@ -122,6 +122,8 @@ - (void) setCASPGTId: (NSString *) pgtId forPGTIOU: (NSString *) pgtIou; +- (void) removeCASSessionWithTicket: (NSString *) ticket; + // SAML2 support - (NSDictionary *) saml2LoginDumpsForIdentifier: (NSString *) identifier; - (void) setSaml2LoginDumps: (NSDictionary *) dump diff --git a/SoObjects/SOGo/SOGoCache.m b/SoObjects/SOGo/SOGoCache.m index afde2d9b4..6e46fa11c 100644 --- a/SoObjects/SOGo/SOGoCache.m +++ b/SoObjects/SOGo/SOGoCache.m @@ -648,6 +648,17 @@ static memcached_st *handle = NULL; forKey: [NSString stringWithFormat: @"cas-pgtiou:%@", pgtIou]]; } +- (void) removeCASSessionWithTicket: (NSString *) ticket +{ + NSString *key, *session; + if ((session = [self CASSessionWithTicket: ticket])) + { + key = [NSString stringWithFormat: @"cas-ticket:%@", ticket]; + [self removeValueForKey: key]; + [self debugWithFormat: @"Removed session: %@", session]; + } +} + // SAML2 support - (NSDictionary *) saml2LoginDumpsForIdentifier: (NSString *) identifier { diff --git a/UI/MainUI/SOGoRootPage.m b/UI/MainUI/SOGoRootPage.m index 337d35e7e..5f10391f4 100644 --- a/UI/MainUI/SOGoRootPage.m +++ b/UI/MainUI/SOGoRootPage.m @@ -272,10 +272,13 @@ { NSDictionary *redirectKeys; NSURL *soURL; + NSString *serviceURL; soURL = [[WOApplication application] soURL]; + // appending 'index' to /SOGo/so/. Matches serviceURL sent by _pgtUrlFromURL + serviceURL = [NSString stringWithFormat: @"%@index", [soURL absoluteString]]; - redirectKeys = [NSDictionary dictionaryWithObject: [soURL absoluteString] + redirectKeys = [NSDictionary dictionaryWithObject: serviceURL forKey: @"service"]; return redirectKeys; @@ -302,7 +305,7 @@ - (id ) _casDefaultAction { WOResponse *response; - NSString *login, *newLocation, *oldLocation, *ticket; + NSString *login, *logoutRequest, *newLocation, *oldLocation, *ticket; SOGoCASSession *casSession; SOGoWebAuthenticator *auth; WOCookie *casCookie, *casLocationCookie; @@ -340,6 +343,18 @@ 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;