Monotone-Parent: 775b7e4fea80568033b5c8bf9b7c5220c7d36041

Monotone-Revision: ff12d30f9bf1800bb0b1bcbefba9493cdeeeccaf

Monotone-Author: wsourdeau@inverse.ca
Monotone-Date: 2010-01-28T21:42:03
Monotone-Branch: ca.inverse.sogo
maint-2.0.2
Wolfgang Sourdeau 2010-01-28 21:42:03 +00:00
parent 331e8a4f7d
commit 126651b6a7
20 changed files with 1034 additions and 102 deletions

View File

@ -1,3 +1,49 @@
2010-01-28 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* SoObjects/SOGo/SOGoSystemDefaults.m (-CASServiceURL)
(-authenticationType): new accessors.
* SoObjects/SOGo/SOGoObject.m (+globallyUniqueObjectId): fixed to
use the result of "random" rather than the function pointer.
* SoObjects/SOGo/SOGoCache.m (-setValue:forKey:expire)
(-setValue:forKey:, -valueForKey:, -removeValueForKey:): new
accessors.
(-CASTicketFromIdentifier:, -CASSessionWithTicket:)
(-setCASSession:withTicket:forIdentifier:): new accessors enabling
the CAS session management.
(-CASPGTIdFromPGTIOU:, -setCASPGTId:forPGTIOU:): new accessors
enabling proxy ticket management (see casProxyAction below).
* UI/MainUI/SOGoUserHomePage.m (-logoffAction): we now pass the
request application name as cookie path.
* UI/MainUI/SOGoRootPage.m (-casProxyAction): new method invoked
by the CAS server when authenticating the SOGo server during a
proxy request.
(-_casDefaultAction): new "defaultAction" method executed in CAS mode.
* UI/Common/UIxPageFrame.m (-canLogoff): return NO in CAS mode.
* SoObjects/SOGo/SOGoWebAuthenticator.m (-checkLogin:password:)
when authentication type is set to "cas", the password is the
ticket identifying the CAS session. We therefore check if the
login and the session login match.
(-imapPasswordInContext:forServer:forceRenew:): new method that
returns the "password" to use for IMAP connections. In CAS mode,
a proxy ticket is fetched with the current CAS session. In
standard mode, we still use the current user's password. The
"forceRenew:" parameter enables the fetching of a new proxy ticket
if the current one has expired.
(-setupAuthFailResponse:withReason:inContext:): we know invoke
"defaultAction" on the SOGoRootPage instance to make sure all the
cookies and CAS tickets are taken into account.
We now pass the request application name as cookie path.
* SoObjects/SOGo/SOGoCASSession.[hm]: new class module
implementing the class in charge of CAS authentication and proxy
transactions.
2010-01-27 Wolfgang Sourdeau <wsourdeau@inverse.ca>
* Tools/SOGoToolBackup.m (-fetchUserIDs): retain allUsers to avoid

View File

@ -225,7 +225,7 @@ static NSString *sieveScriptName = @"sogo";
SOGoUserDefaults *ud;
SOGoDomainDefaults *dd;
NGSieveClient *client;
NSString *v;
NSString *v, *password;
BOOL b;
dd = [[context activeUser] domainDefaults];
@ -319,11 +319,21 @@ static NSString *sieveScriptName = @"sogo";
return NO;
}
result = [client login: [[self imap4URL] user] password:[self imap4Password]];
password = [self imap4PasswordRenewed: NO];
if (!password) {
[client closeConnection];
return NO;
}
result = [client login: [[self imap4URL] user] password: password];
if (![[result valueForKey:@"result"] boolValue]) {
[self errorWithFormat: @"failure. Attempting with a renewed password."];
password = [self imap4PasswordRenewed: NO];
result = [client login: [[self imap4URL] user] password: password];
}
if (![[result valueForKey:@"result"] boolValue]) {
[self errorWithFormat: @"Could not login '%@' (%@) on Sieve server: %@: %@",
[[self imap4URL] user], [self imap4Password], client, result];
[[self imap4URL] user], password, client, result];
[client closeConnection];
return NO;
}

View File

@ -70,7 +70,7 @@
- (NSURL *) imap4URL;
- (NSString *) imap4Login;
- (NSString *) imap4Password;
- (NSString *) imap4PasswordRenewed: (BOOL) renew;
- (void) flushMailCaches;

View File

@ -30,7 +30,7 @@
#import <NGExtensions/NSString+misc.h>
#import <NGExtensions/NSURL+misc.h>
#import <SoObjects/SOGo/SOGoAuthenticator.h>
#import <SOGo/SOGoAuthenticator.h>
#import "SOGoMailManager.h"
@ -119,17 +119,36 @@ static BOOL debugOn = YES;
- (NGImap4Connection *) imap4Connection
{
NGImap4ConnectionManager *manager;
NSString *password;
if (!imap4)
{
imap4 = [[self mailManager] connectionForURL: [self imap4URL]
password: [self imap4Password]];
if (imap4)
[imap4 retain];
else
[self errorWithFormat:@"Could not connect IMAP4."];
[self imap4URL];
manager = [self mailManager];
password = [self imap4PasswordRenewed: NO];
if (password)
{
imap4 = [manager connectionForURL: imap4URL
password: password];
if (!imap4)
{
[self logWithFormat: @"renewing imap4 password"];
password = [self imap4PasswordRenewed: YES];
if (password)
imap4 = [manager connectionForURL: imap4URL
password: password];
}
}
if (!imap4)
{
imap4 = (NGImap4Connection *) [NSNull null];
[self errorWithFormat:@"Could not connect IMAP4"];
}
[imap4 retain];
}
return imap4;
return [imap4 isKindOfClass: [NSNull class]] ? nil : imap4;
}
- (NSString *) relativeImap4Name
@ -184,7 +203,7 @@ static BOOL debugOn = YES;
return [container imap4Login];
}
- (NSString *) imap4Password
- (NSString *) imap4PasswordRenewed: (BOOL) renewed
{
/*
Extract password from basic authentication.
@ -193,8 +212,19 @@ static BOOL debugOn = YES;
a) move the primary code to SOGoMailAccount
b) cache the password
*/
NSURL *imapURL;
NSString *password;
return [[self authenticatorInContext: context] passwordInContext: context];
imapURL = [[self mailAccountFolder] imap4URL];
password = [[self authenticatorInContext: context]
imapPasswordInContext: context
forServer: [imapURL host]
forceRenew: renewed];
if (!password)
[self errorWithFormat: @"no IMAP4 password available"];
return password;
}
- (NSMutableString *) traversalFromMailAccount

View File

@ -931,14 +931,35 @@ static BOOL debugSoParts = NO;
doesn't tell us the new ID.
*/
NSURL *destImap4URL;
NGImap4ConnectionManager *manager;
NSException *exc;
NSString *password;
destImap4URL = ([_name length] == 0)
? [[_target container] imap4URL]
: [_target imap4URL];
return [[self mailManager] copyMailURL:[self imap4URL]
toFolderURL:destImap4URL
password:[self imap4Password]];
manager = [self mailManager];
[self imap4URL];
password = [self imap4PasswordRenewed: NO];
if (password)
{
exc = [manager copyMailURL: imap4URL
toFolderURL: destImap4URL
password: password];
if (exc)
{
[self
logWithFormat: @"failure. Attempting with renewed imap4 password"];
password = [self imap4PasswordRenewed: YES];
if (password)
exc = [manager copyMailURL: imap4URL
toFolderURL: destImap4URL
password: password];
}
}
return exc;
}
/* actions */

View File

@ -46,6 +46,7 @@ SOGo_HEADER_FILES = \
NSURL+DAV.h \
\
SOGoAuthenticator.h \
SOGoCASSession.h \
SOGoDAVAuthenticator.h \
SOGoProxyAuthenticator.h \
SOGoWebAuthenticator.h \
@ -105,8 +106,9 @@ SOGo_OBJC_FILES = \
NSString+Utilities.m \
NSURL+DAV.m \
\
SOGoCASSession.m \
SOGoDAVAuthenticator.m \
SOGoProxyAuthenticator.m \
SOGoProxyAuthenticator.m \
SOGoWebAuthenticator.m \
SOGoWebDAVAclManager.m \
SOGoWebDAVValue.m \

View File

@ -33,6 +33,9 @@
- (NSString *) passwordInContext: (WOContext *) context;
- (SOGoUser *) userInContext: (WOContext *) context;
- (NSString *) imapPasswordInContext: (WOContext *) context
forServer: (NSString *) imapServer
forceRenew: (BOOL) renew;
@end

View File

@ -0,0 +1,66 @@
/* SOGoCASSession.h - this file is part of SOGo
*
* Copyright (C) 2010 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
*
* 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 SOGOCASSESSION_H
#define SOGOCASSESSION_H
/* implementation of the CAS protocol as required for a client/proxy:
http://www.jasig.org/cas/protocol */
#import <Foundation/NSObject.h>
@class NSString;
@class NSMutableDictionary;
@interface SOGoCASSession : NSObject
{
NSString *ticket;
NSString *login;
NSString *pgt;
NSString *identifier;
NSMutableDictionary *proxyTickets;
BOOL cacheUpdateNeeded;
NSString *currentProxyService;
}
+ (NSString *) CASURLWithAction: (NSString *) casAction
andParameters: (NSDictionary *) parameters;
+ (SOGoCASSession *) CASSessionWithTicket: (NSString *) newTicket;
+ (SOGoCASSession *) CASSessionWithIdentifier: (NSString *) identifier;
- (NSString *) identifier;
- (void) setTicket: (NSString *) newTicket;
- (NSString *) ticket;
- (NSString *) login;
- (NSString *) identifier;
- (NSString *) ticketForService: (NSString *) service;
- (void) invalidateTicketForService: (NSString *) service;
- (void) updateCache;
@end
#endif /* SOGOCASSESSION_H */

View File

@ -0,0 +1,454 @@
/* SOGoCASSession.m - this file is part of SOGo
*
* Copyright (C) 2010 Inverse inc.
*
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
*
* 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 <Foundation/NSDictionary.h>
#import <Foundation/NSURL.h>
#import <DOM/DOMElement.h>
#import <DOM/DOMDocument.h>
#import <DOM/DOMProtocols.h>
#import <DOM/DOMText.h>
#import <NGObjWeb/WOApplication.h>
#import <NGObjWeb/WOHTTPConnection.h>
#import <NGObjWeb/WORequest.h>
#import <NGObjWeb/WOContext.h>
#import <NGObjWeb/WOResponse.h>
#import <NGExtensions/NSObject+Logs.h>
#import "NSDictionary+BSJSONAdditions.h"
#import "NSString+Utilities.h"
#import "SOGoCache.h"
#import "SOGoObject.h"
#import "SOGoSystemDefaults.h"
#import "SOGoCASSession.h"
@implementation SOGoCASSession
+ (NSString *) CASURLWithAction: (NSString *) casAction
andParameters: (NSDictionary *) parameters
{
NSString *casActionURL, *baseCASURL;
SOGoSystemDefaults *sd;
sd = [SOGoSystemDefaults sharedSystemDefaults];
baseCASURL = [sd CASServiceURL];
if ([baseCASURL length])
casActionURL = [baseCASURL composeURLWithAction: casAction
parameters: parameters
andHash: NO];
else
{
[self errorWithFormat:
@"'SOGoCASServiceURL' is empty in the user defaults"];
casActionURL = nil;
}
return casActionURL;
}
+ (SOGoCASSession *) CASSessionWithTicket: (NSString *) newTicket
{
SOGoCASSession *newSession;
if (newTicket)
{
newSession = [self new];
[newSession autorelease];
[newSession setTicket: newTicket];
}
else
newSession = nil;
return newSession;
}
+ (SOGoCASSession *) CASSessionWithIdentifier: (NSString *) identifier
{
SOGoCASSession *session;
SOGoCache *cache;
NSString *ticket;
cache = [SOGoCache sharedCache];
ticket = [cache CASTicketFromIdentifier: identifier];
session = [self CASSessionWithTicket: ticket];
return session;
}
- (id) init
{
if ((self = [super init]))
{
ticket = nil;
login = nil;
pgt = nil;
identifier = nil;
proxyTickets = nil;
cacheUpdateNeeded = NO;
}
return self;
}
- (void) dealloc
{
[login release];
[pgt release];
[ticket release];
[proxyTickets release];
[super dealloc];
}
- (void) _loadSessionFromCache
{
SOGoCache *cache;
NSString *jsonSession;
NSDictionary *sessionDict;
cache = [SOGoCache sharedCache];
jsonSession = [cache CASSessionWithTicket: ticket];
if ([jsonSession length])
{
sessionDict = [NSMutableDictionary dictionaryWithJSONString: jsonSession];
ASSIGN (login, [sessionDict objectForKey: @"login"]);
ASSIGN (pgt, [sessionDict objectForKey: @"pgt"]);
ASSIGN (identifier, [sessionDict objectForKey: @"identifier"]);
ASSIGN (proxyTickets, [sessionDict objectForKey: @"proxyTickets"]);
if (!proxyTickets)
proxyTickets = [NSMutableDictionary new];
}
else
cacheUpdateNeeded = YES;
}
- (void) _saveSessionToCache
{
SOGoCache *cache;
NSString *jsonSession;
NSMutableDictionary *sessionDict;
cache = [SOGoCache sharedCache];
sessionDict = [NSMutableDictionary dictionary];
[sessionDict setObject: login forKey: @"login"];
if (pgt)
[sessionDict setObject: pgt forKey: @"pgt"];
[sessionDict setObject: identifier forKey: @"identifier"];
if ([proxyTickets count])
[sessionDict setObject: proxyTickets forKey: @"proxyTickets"];
jsonSession = [sessionDict jsonStringValue];
[cache setCASSession: jsonSession
withTicket: ticket
forIdentifier: identifier];
}
- (void) setTicket: (NSString *) newTicket
{
ASSIGN (ticket, newTicket);
[self _loadSessionFromCache];
}
- (NSString *) ticket
{
return ticket;
}
- (void) _parseSuccessElement: (NGDOMElement *) element
{
NSString *tagName, *pgtIou;
NGDOMText *valueNode;
SOGoCache *cache;
tagName = [element tagName];
valueNode = (NGDOMText *) [element firstChild];
if ([valueNode nodeType] == DOM_TEXT_NODE)
{
if ([tagName isEqualToString: @"user"])
ASSIGN (login, [valueNode nodeValue]);
else if ([tagName isEqualToString: @"proxyGrantingTicket"])
{
pgtIou = [valueNode nodeValue];
cache = [SOGoCache sharedCache];
ASSIGN (pgt, [cache CASPGTIdFromPGTIOU: pgtIou]);
}
else
[self logWithFormat: @"unhandled success tag '%@'", tagName];
}
}
- (void) _parseProxySuccessElement: (NGDOMElement *) element
{
NSString *tagName;
NGDOMText *valueNode;
tagName = [element tagName];
if ([tagName isEqualToString: @"proxyTicket"])
{
valueNode = (NGDOMText *) [element firstChild];
if ([valueNode nodeType] == DOM_TEXT_NODE)
{
[proxyTickets setObject: [valueNode nodeValue]
forKey: currentProxyService];
cacheUpdateNeeded = YES;
}
}
else
[self logWithFormat: @"unhandled proxy success tag '%@'", tagName];
}
- (void) _parseProxyFailureElement: (NGDOMElement *) element
{
NSMutableString *errorString;
NSString *errorText;
NGDOMText *valueNode;
errorString = [NSMutableString stringWithString: (@"a CAS failure occured"
@" during operation")];
if ([element hasAttribute: @"code"])
[errorString appendFormat: @" (code: '%@')",
[element attribute: @"code"]];
valueNode = (NGDOMText *) [element firstChild];
if (valueNode)
{
[errorString appendString: @":"];
while (valueNode)
{
if ([valueNode nodeType] == DOM_TEXT_NODE)
{
errorText = [[valueNode nodeValue] stringByTrimmingSpaces];
[errorString appendFormat: @" %@", errorText];
}
valueNode = (NGDOMText *) [valueNode nextSibling];
}
}
[self logWithFormat: errorString];
}
- (SEL) _selectorForSubElementsOfTag: (NSString *) tag
{
static NSMutableDictionary *mapping = nil;
NSString *methodName;
SEL selector;
if (!mapping)
{
mapping = [NSMutableDictionary new];
[mapping setObject: @"_parseSuccessElement:"
forKey: @"authenticationSuccess"];
[mapping setObject: @"_parseProxySuccessElement:"
forKey: @"proxySuccess"];
}
methodName = [mapping objectForKey: tag];
if (methodName)
selector = NSSelectorFromString (methodName);
else
{
selector = NULL;
[self errorWithFormat: @"unhandled response tag '%@'", tag];
}
return selector;
}
- (void) _parseResponseElement: (NGDOMElement *) element
{
id <DOMNodeList> nodes;
NGDOMElement *currentNode;
SEL parseElementSelector;
NSString *tagName;
int count, max;
tagName = [element tagName];
if ([tagName isEqualToString: @"proxyFailure"])
[self _parseProxyFailureElement: element];
else
{
parseElementSelector = [self _selectorForSubElementsOfTag: tagName];
if (parseElementSelector)
{
nodes = [element childNodes];
max = [nodes length];
for (count = 0; count < max; count++)
{
currentNode = [nodes objectAtIndex: count];
if ([currentNode nodeType] == DOM_ELEMENT_NODE)
[self performSelector: parseElementSelector
withObject: currentNode];
}
}
}
}
- (void) _parseDOMResponse: (NGDOMDocument *) response
{
NGDOMElement *top;
id <DOMNodeList> nodes;
NGDOMElement *currentNode;
int count, max;
top = [response documentElement];
nodes = [top childNodes];
max = [nodes length];
for (count = 0; count < max; count++)
{
currentNode = [nodes objectAtIndex: count];
if ([currentNode nodeType] == DOM_ELEMENT_NODE)
[self _parseResponseElement: currentNode];
}
}
- (void) _performCASRequestWithAction: (NSString *) casAction
andParameters: (NSDictionary *) parameters
{
NSString *requestURL;
NSURL *url;
WORequest *request;
WOResponse *response;
WOHTTPConnection *httpConnection;
requestURL = [[self class] CASURLWithAction: casAction
andParameters: parameters];
if (requestURL)
{
url = [NSURL URLWithString: requestURL];
httpConnection = [[WOHTTPConnection alloc]
initWithURL: url];
[httpConnection autorelease];
request = [[WORequest alloc] initWithMethod: @"GET"
uri: [requestURL hostlessURL]
httpVersion: @"HTTP/1.1"
headers: nil content: nil
userInfo: nil];
[request autorelease];
[httpConnection sendRequest: request];
response = [httpConnection readResponse];
[self _parseDOMResponse: [response contentAsDOMDocument]];
}
}
/* returns the URL that matches -[SOGoRootPage casProxyAction] */
- (NSString *) _pgtUrlFromURL: (NSURL *) soURL
{
WOApplication *application;
NSString *pgtURL;
WORequest *request;
application = [WOApplication application];
request = [[application context] request];
pgtURL = [NSString stringWithFormat:
@"https://%@/%@/casProxy",
[soURL host], [request applicationName]];
return pgtURL;
}
- (void) _fetchTicketData
{
NSDictionary *params;
NSURL *soURL;
NSString *serviceURL;
soURL = [[WOApplication application] soURL];
serviceURL = [soURL absoluteString];
params = [NSDictionary dictionaryWithObjectsAndKeys:
ticket, @"ticket", serviceURL, @"service",
[self _pgtUrlFromURL: soURL], @"pgtUrl",
nil];
[self _performCASRequestWithAction: @"serviceValidate"
andParameters: params];
identifier = [SOGoObject globallyUniqueObjectId];
[identifier retain];
cacheUpdateNeeded = YES;
}
- (NSString *) login
{
if (!login)
[self _fetchTicketData];
return login;
}
- (NSString *) identifier
{
return identifier;
}
- (void) updateCache
{
if (cacheUpdateNeeded)
{
[self _saveSessionToCache];
cacheUpdateNeeded = NO;
}
}
- (void) _fetchTicketDataForService: (NSString *) service
{
NSDictionary *params;
params = [NSDictionary dictionaryWithObjectsAndKeys:
pgt, @"pgt", service, @"targetService",
nil];
[self _performCASRequestWithAction: @"proxy"
andParameters: params];
}
- (NSString *) ticketForService: (NSString *) service
{
NSString *proxyTicket;
if (pgt)
{
proxyTicket = [proxyTickets objectForKey: service];
if (!proxyTicket)
{
currentProxyService = service;
[self _fetchTicketDataForService: service];
proxyTicket = [proxyTickets objectForKey: service];
if (proxyTicket)
cacheUpdateNeeded = YES;
currentProxyService = nil;
}
}
else
{
[self errorWithFormat: @"attempted to obtain a ticket for service '%@'"
@" while no PGT available", service];
proxyTicket = nil;
}
return proxyTicket;
}
- (void) invalidateTicketForService: (NSString *) service
{
[proxyTickets removeObjectForKey: service];
cacheUpdateNeeded = YES;
}
@end

View File

@ -62,6 +62,11 @@
withName: (NSString *) userName;
- (id) userNamed: (NSString *) name;
/* NSDictionary-like methods */
- (void) setValue: (NSString *) value forKey: (NSString *) key;
- (NSString *) valueForKey: (NSString *) key;
- (void) removeValueForKey: (NSString *) key;
- (void) setUserAttributes: (NSString *) attributes
forLogin: (NSString *) login;
- (NSString *) userAttributesForLogin: (NSString *) theLogin;
@ -74,6 +79,17 @@
forLogin: (NSString *) login;
- (NSString *) userSettingsForLogin: (NSString *) theLogin;
/* CAS support */
- (NSString *) CASTicketFromIdentifier: (NSString *) identifier;
- (NSString *) CASSessionWithTicket: (NSString *) ticket;
- (void) setCASSession: (NSString *) casSession
withTicket: (NSString *) ticket
forIdentifier: (NSString *) identifier;
- (NSString *) CASPGTIdFromPGTIOU: (NSString *) pgtIou;
- (void) setCASPGTId: (NSString *) pgtId
forPGTIOU: (NSString *) pgtIou;
@end
#endif /* SOGOCACHE_H */

View File

@ -119,8 +119,8 @@
- (void) dealloc
{
memcached_server_free(servers);
memcached_free(handle);
memcached_server_free (servers);
memcached_free (handle);
[memcachedServerName release];
[cache release];
[users release];
@ -203,73 +203,124 @@
// For non-blocking cache method, see memcached_behavior_set and MEMCACHED_BEHAVIOR_NO_BLOCK
// memcached is thread-safe so no need to lock here.
//
- (void) _cacheValues: (NSString *) theAttributes
ofType: (NSString *) theType
forLogin: (NSString *) theLogin
- (void) setValue: (NSString *) value
forKey: (NSString *) key
expire: (float) expiration
{
NSData *keyData, *valueData;
memcached_return error;
NSString *keyName;
NSData *key, *value;
keyName = [NSString stringWithFormat: @"%@+%@", theLogin, theType];
if (handle)
{
key = [keyName dataUsingEncoding: NSUTF8StringEncoding];
value = [theAttributes dataUsingEncoding: NSUTF8StringEncoding];
error = memcached_set(handle,
[key bytes], [key length],
[value bytes], [value length],
cleanupInterval, 0);
[localCache setObject: theAttributes forKey: keyName];
keyData = [key dataUsingEncoding: NSUTF8StringEncoding];
valueData = [value dataUsingEncoding: NSUTF8StringEncoding];
error = memcached_set (handle,
[keyData bytes], [keyData length],
[valueData bytes], [valueData length],
expiration, 0);
if (error != MEMCACHED_SUCCESS)
[self logWithFormat: @"memcached error: unable to cache values with subtype '%@' for user '%@'", theType, theLogin];
[self logWithFormat:
@"memcached error: unable to cache values for key '%@'",
key];
//else
//[self logWithFormat: @"memcached: cached values (%s) with subtype %@
//for user %@", value, theType, theLogin];
}
else
[self errorWithFormat: @"attempting to cache value for key '%@' while"
" no handle exists", keyName];
[self errorWithFormat: (@"attempting to cache value for key '%@' while"
" no handle exists"), key];
}
- (void) setValue: (NSString *) value
forKey: (NSString *) key
{
[self setValue: value forKey: key
expire: cleanupInterval];
}
- (NSString *) valueForKey: (NSString *) key
{
NSString *valueString;
NSData *keyData;
char *value;
size_t vlen;
memcached_return rc;
unsigned int flags;
if (handle)
{
keyData = [key dataUsingEncoding: NSUTF8StringEncoding];
value = memcached_get (handle, [keyData bytes], [keyData length],
&vlen, &flags, &rc);
if (rc == MEMCACHED_SUCCESS && value)
{
valueString
= [[NSString alloc] initWithBytesNoCopy: value
length: vlen
encoding: NSUTF8StringEncoding
freeWhenDone: YES];
[valueString autorelease];
}
else
valueString = nil;
}
else
{
valueString = nil;
[self errorWithFormat: @"attempting to retrieved cached value for key"
@" '%@' while no handle exists", key];
}
return valueString;
}
- (void) removeValueForKey: (NSString *) key
{
NSData *keyData;
memcached_return rc;
if (handle)
{
keyData = [key dataUsingEncoding: NSUTF8StringEncoding];
rc = memcached_delete (handle, [keyData bytes], [keyData length],
0);
if (rc != MEMCACHED_SUCCESS)
[self errorWithFormat: (@"failure deleting cached value for key"
@" '%@'"),
key];
}
else
[self errorWithFormat: (@"attempting to delete cached value for key"
@" '%@' while no handle exists"),
key];
}
- (void) _cacheValues: (NSString *) theAttributes
ofType: (NSString *) theType
forLogin: (NSString *) theLogin
{
NSString *keyName;
keyName = [NSString stringWithFormat: @"%@+%@", theLogin, theType];
[self setValue: theAttributes forKey: keyName];
[localCache setObject: theAttributes forKey: keyName];
}
- (NSString *) _valuesOfType: (NSString *) theType
forLogin: (NSString *) theLogin
{
NSString *valueString, *keyName;
NSData *key;
char *value;
size_t vlen;
memcached_return rc;
unsigned int flags;
valueString = nil;
keyName = [NSString stringWithFormat: @"%@+%@", theLogin, theType];
if (handle)
valueString = [localCache objectForKey: keyName];
if (!valueString)
{
valueString = [localCache objectForKey: keyName];
if (!valueString)
{
key = [keyName dataUsingEncoding: NSUTF8StringEncoding];
value = memcached_get (handle, [key bytes], [key length],
&vlen, &flags, &rc);
if (rc == MEMCACHED_SUCCESS && value)
{
valueString
= [[NSString alloc] initWithBytesNoCopy: value
length: vlen
encoding: NSUTF8StringEncoding
freeWhenDone: YES];
[valueString autorelease];
// Cache the value in our localCache
[localCache setObject: valueString forKey: keyName];
}
}
valueString = [self valueForKey: keyName];
if (valueString)
[localCache setObject: valueString forKey: keyName];
}
else
[self errorWithFormat: @"attempting to retrieved cached value for key"
@" '%@' while no handle exists", keyName];
return valueString;
}
@ -310,4 +361,47 @@
return [self _valuesOfType: @"settings" forLogin: theLogin];
}
/* CAS session support */
- (NSString *) CASTicketFromIdentifier: (NSString *) identifier
{
return [self valueForKey: [NSString stringWithFormat: @"cas-id:%@",
identifier]];
}
- (NSString *) CASSessionWithTicket: (NSString *) ticket
{
return [self valueForKey: [NSString stringWithFormat: @"cas-ticket:%@",
ticket]];
}
- (void) setCASSession: (NSString *) casSession
withTicket: (NSString *) ticket
forIdentifier: (NSString *) identifier
{
[self setValue: ticket
forKey: [NSString stringWithFormat: @"cas-id:%@", identifier]];
[self setValue: casSession
forKey: [NSString stringWithFormat: @"cas-ticket:%@", ticket]];
}
- (NSString *) CASPGTIdFromPGTIOU: (NSString *) pgtIou
{
NSString *casPgtId, *key;
key = [NSString stringWithFormat: @"cas-pgtiou:%@", pgtIou];
casPgtId = [self valueForKey: key];
/* we directly remove the value as it can only be used once anyway */
if (casPgtId)
[self removeValueForKey: key];
return casPgtId;
}
- (void) setCASPGTId: (NSString *) pgtId
forPGTIOU: (NSString *) pgtIou
{
[self setValue: pgtId
forKey: [NSString stringWithFormat: @"cas-pgtiou:%@", pgtIou]];
}
@end

View File

@ -69,6 +69,13 @@
return password;
}
- (NSString *) imapPasswordInContext: (WOContext *) context
forServer: (NSString *) imapServer
forceRenew: (BOOL) renew
{
return [self passwordInContext: context];
}
/* create SOGoUser */
- (SOGoUser *) userInContext: (WOContext *)_ctx

View File

@ -129,7 +129,7 @@
f = [[NSDate date] timeIntervalSince1970];
return [NSString stringWithFormat:@"%0X-%0X-%0X-%0X",
pid, (int) f, sequence++, random];
pid, (int) f, sequence++, (int) rndm];
}
- (NSString *) globallyUniqueObjectId

View File

@ -60,6 +60,10 @@
- (NSArray *) supportedLanguages;
- (NSString *) loginSuffix;
- (NSString *) authenticationType;
- (NSString *) CASServiceURL;
@end
#endif /* SOGOSYSTEMDEFAULTS_H */

View File

@ -285,4 +285,14 @@ BootstrapNSUserDefaults ()
return [self stringForKey: @"SOGoLoginSuffix"];
}
- (NSString *) authenticationType
{
return [[self stringForKey: @"SOGoAuthenticationType"] lowercaseString];
}
- (NSString *) CASServiceURL
{
return [self stringForKey: @"SOGoCASServiceURL"];
}
@end

View File

@ -36,9 +36,11 @@
#import <MainUI/SOGoRootPage.h>
#import "SOGoUserManager.h"
#import "SOGoCASSession.h"
#import "SOGoPermissions.h"
#import "SOGoSystemDefaults.h"
#import "SOGoUser.h"
#import "SOGoUserManager.h"
#import "SOGoWebAuthenticator.h"
@ -57,8 +59,24 @@
- (BOOL) checkLogin: (NSString *) _login
password: (NSString *) _pwd
{
return [[SOGoUserManager sharedUserManager] checkLogin: _login
SOGoSystemDefaults *sd;
BOOL rc;
SOGoCASSession *session;
sd = [SOGoSystemDefaults sharedSystemDefaults];
if ([[sd authenticationType] isEqualToString: @"cas"])
{
session = [SOGoCASSession CASSessionWithIdentifier: _pwd];
if (session)
rc = [[session login] isEqualToString: _login];
else
rc = NO;
}
else
rc = [[SOGoUserManager sharedUserManager] checkLogin: _login
andPassword: _pwd];
return rc;
}
- (SOGoUser *) userInContext: (WOContext *)_ctx
@ -83,7 +101,7 @@
{
NSArray *creds;
NSString *auth, *password;
auth = [[context request]
cookieValueForKey: [self cookieNameInContext: context]];
creds = [self parseCredentials: auth];
@ -95,6 +113,33 @@
return password;
}
- (NSString *) imapPasswordInContext: (WOContext *) context
forServer: (NSString *) imapServer
forceRenew: (BOOL) renew
{
SOGoSystemDefaults *sd;
SOGoCASSession *session;
NSString *password, *service;
password = [self passwordInContext: context];
if ([password length])
{
sd = [SOGoSystemDefaults sharedSystemDefaults];
if ([[sd authenticationType] isEqualToString: @"cas"])
{
session = [SOGoCASSession CASSessionWithIdentifier: password];
service = [NSString stringWithFormat: @"imap://%@", imapServer];
if (renew)
[session invalidateTicketForService: service];
password = [session ticketForService: service];
if ([password length] || renew)
[session updateCache];
}
}
return password;
}
/* create SOGoUser */
- (SOGoUser *) userWithLogin: (NSString *) login
@ -113,7 +158,7 @@
*/
WOResponse *response;
NSString *auth;
auth = [[context request]
cookieValueForKey: [self cookieNameInContext:context]];
if ([auth isEqualToString: @"discard"])
@ -133,16 +178,20 @@
inContext: (WOContext *) context
{
WOComponent *page;
WORequest *request;
WOCookie *authCookie;
NSCalendarDate *date;
NSString *appName;
request = [context request];
page = [[WOApplication application] pageWithName: @"SOGoRootPage"
forRequest: [context request]];
[[SoDefaultRenderer sharedRenderer] renderObject: page
forRequest: request];
[[SoDefaultRenderer sharedRenderer] renderObject: [page defaultAction]
inContext: context];
authCookie = [WOCookie cookieWithName: [self cookieNameInContext: context]
value: @"discard"];
[authCookie setPath: @"/"];
appName = [request applicationName];
[authCookie setPath: [NSString stringWithFormat: @"/%@/", appName]];
date = [NSCalendarDate calendarDate];
[authCookie setExpires: [date yesterday]];
[response addCookie: authCookie];

View File

@ -409,10 +409,15 @@
{
BOOL canLogoff;
id auth;
SOGoSystemDefaults *sd;
auth = [[self clientObject] authenticatorInContext: context];
if ([auth respondsToSelector: @selector (cookieNameInContext:)])
canLogoff = ([[auth cookieNameInContext: context] length] > 0);
{
sd = [SOGoSystemDefaults sharedSystemDefaults];
canLogoff = ([[auth cookieNameInContext: context] length] > 0
&& ![[sd authenticationType] isEqualToString: @"cas"]);
}
else
canLogoff = NO;

View File

@ -19,8 +19,11 @@
02111-1307, USA.
*/
#import <Foundation/NSDictionary.h>
#import <Foundation/NSException.h>
#import <Foundation/NSURL.h>
#import <NGObjWeb/NSException+HTTP.h>
#import <NGObjWeb/WOApplication.h>
#import <NGObjWeb/WOContext.h>
#import <NGObjWeb/WOCookie.h>
@ -32,6 +35,9 @@
#import <NGExtensions/NSString+misc.h>
#import <NGExtensions/NSObject+Logs.h>
#import <SOGo/NSDictionary+Utilities.h>
#import <SOGo/SOGoCache.h>
#import <SOGo/SOGoCASSession.h>
#import <SOGo/SOGoDomainDefaults.h>
#import <SOGo/SOGoSystemDefaults.h>
#import <SOGo/SOGoUser.h>
@ -54,6 +60,27 @@
return [NSString stringWithFormat: @"%@/connect", [self applicationPath]];
}
- (WOCookie *) _cookieWithUsername: (NSString *) username
andPassword: (NSString *) password
forAuthenticator: (SOGoWebAuthenticator *) auth
{
WOCookie *authCookie;
NSString *cookieValue, *cookieString, *appName;
cookieString = [NSString stringWithFormat: @"%@:%@",
username, password];
cookieValue = [NSString stringWithFormat: @"basic %@",
[cookieString stringByEncodingBase64]];
authCookie = [WOCookie cookieWithName: [auth cookieNameInContext: context]
value: cookieValue];
appName = [[context request] applicationName];
[authCookie setPath: [NSString stringWithFormat: @"/%@/", appName]];
/* enable this when we have code to determine whether request is HTTPS:
[authCookie setIsSecure: YES]; */
return authCookie;
}
/* actions */
- (id <WOActionResults>) connectAction
{
@ -62,70 +89,154 @@
WOCookie *authCookie;
SOGoWebAuthenticator *auth;
SOGoUserDefaults *ud;
NSString *cookieValue, *cookieString;
NSString *userName, *password, *language;
NSString *username, *password, *language;
NSArray *supportedLanguages;
auth = [[WOApplication application]
authenticatorInContext: context];
request = [context request];
userName = [request formValueForKey: @"userName"];
username = [request formValueForKey: @"userName"];
password = [request formValueForKey: @"password"];
language = [request formValueForKey: @"language"];
if ([auth checkLogin: userName password: password])
if ([auth checkLogin: username password: password])
{
[self logWithFormat: @"successful login for user '%@'", userName];
[self logWithFormat: @"successful login for user '%@'", username];
response = [self responseWith204];
cookieString = [NSString stringWithFormat: @"%@:%@",
userName, password];
cookieValue = [NSString stringWithFormat: @"basic %@",
[cookieString stringByEncodingBase64]];
authCookie = [WOCookie cookieWithName: [auth cookieNameInContext: context]
value: cookieValue];
[authCookie setPath: @"/"];
/* enable this when we have code to determine whether request is HTTPS:
[authCookie setIsSecure: YES]; */
authCookie = [self _cookieWithUsername: username andPassword: password
forAuthenticator: auth];
[response addCookie: authCookie];
supportedLanguages = [[SOGoSystemDefaults sharedSystemDefaults]
supportedLanguages];
if (language && [supportedLanguages containsObject: language])
{
ud = [[SOGoUser userWithLogin: userName roles: nil]
userDefaults];
ud = [[SOGoUser userWithLogin: username] userDefaults];
[ud setLanguage: language];
[ud synchronize];
}
}
else
{
[self logWithFormat: @"failed login for user '%@'", userName];
[self logWithFormat: @"failed login for user '%@'", username];
response = [self responseWithStatus: 403];
}
return response;
}
- (id <WOActionResults>) defaultAction
- (NSDictionary *) _casRedirectKeys
{
id <WOActionResults> response;
NSDictionary *redirectKeys;
NSURL *soURL;
soURL = [[WOApplication application] soURL];
redirectKeys = [NSDictionary dictionaryWithObject: [soURL absoluteString]
forKey: @"service"];
return redirectKeys;
}
- (id <WOActionResults>) casProxyAction
{
SOGoCache *cache;
WORequest *request;
NSString *pgtId, *pgtIou;
request = [context request];
pgtId = [request formValueForKey: @"pgtId"];
pgtIou = [request formValueForKey: @"pgtIou"];
if ([pgtId length] && [pgtIou length])
{
cache = [SOGoCache sharedCache];
[cache setCASPGTId: pgtId forPGTIOU: pgtIou];
}
return [self responseWithStatus: 200];
}
- (id <WOActionResults>) _casDefaultAction
{
WOResponse *response;
NSString *login, *newLocation, *oldLocation, *ticket;
SOGoCASSession *casSession;
SOGoWebAuthenticator *auth;
WOCookie *casCookie;
casCookie = nil;
login = [[context activeUser] login];
if ([login isEqualToString: @"anonymous"])
login = nil;
if (!login)
{
ticket = [[context request] formValueForKey: @"ticket"];
if ([ticket length])
{
casSession = [SOGoCASSession CASSessionWithTicket: ticket];
login = [casSession login];
if ([login length])
{
auth = [[WOApplication application]
authenticatorInContext: context];
casCookie = [self _cookieWithUsername: login
andPassword: [casSession identifier]
forAuthenticator: auth];
[casSession updateCache];
}
}
}
if (login)
{
oldLocation = [[self clientObject] baseURLInContext: context];
newLocation = [NSString stringWithFormat: @"%@%@",
oldLocation, [login stringByEscapingURL]];
}
else
newLocation = [SOGoCASSession CASURLWithAction: @"login"
andParameters: [self _casRedirectKeys]];
response = [self redirectToLocation: newLocation];
if (casCookie)
[response addCookie: casCookie];
return response;
}
- (id <WOActionResults>) _standardDefaultAction
{
NSObject <WOActionResults> *response;
NSString *login, *oldLocation;
login = [[context activeUser] login];
if (!login || [login isEqualToString: @"anonymous"])
response = self;
else
if ([login isEqualToString: @"anonymous"])
login = nil;
if (login)
{
oldLocation = [[self clientObject] baseURLInContext: context];
response
= [self redirectToLocation: [NSString stringWithFormat: @"%@/%@",
= [self redirectToLocation: [NSString stringWithFormat: @"%@%@",
oldLocation,
[login stringByEscapingURL]]];
}
else
response = self;
return response;
}
- (id <WOActionResults>) defaultAction
{
SOGoSystemDefaults *sd;
sd = [SOGoSystemDefaults sharedSystemDefaults];
return ([[sd authenticationType] isEqualToString: @"cas"]
? [self _casDefaultAction]
: [self _standardDefaultAction]);
}
- (BOOL) isPublicInContext: (WOContext *) localContext
{
return YES;

View File

@ -224,8 +224,6 @@
WOResponse *response;
response = [self responseWithStatus: 200];
// [response setHeader: @"text/plain; charset=iso-8859-1"
// forKey: @"Content-Type"];
[response appendContentString: [self _freeBusyAsText]];
return response;
@ -238,7 +236,7 @@
SOGoWebAuthenticator *auth;
id container;
NSCalendarDate *date;
NSString *userName, *cookieName;
NSString *userName, *cookieName, *appName;
container = [[self clientObject] container];
@ -261,7 +259,8 @@
if ([cookieName length])
{
cookie = [WOCookie cookieWithName: cookieName value: @"discard"];
[cookie setPath: @"/"];
appName = [[context request] applicationName];
[cookie setPath: [NSString stringWithFormat: @"/%@/", appName]];
[cookie setExpires: [date yesterday]];
[response addCookie: cookie];
}

View File

@ -105,6 +105,11 @@
protectedBy = "<public>";
pageName = "SOGoRootPage";
};
casProxy = {
protectedBy = "<public>";
pageName = "SOGoRootPage";
actionName = "casProxy";
};
/* crash = {
protectedBy = "<public>";
pageName = "SOGoRootPage";