See ChangeLog
Monotone-Parent: 024380d579a482e49866c36ef54561cf8b39ab02 Monotone-Revision: 2cbc46c16f9b3d45d868b15a968f614dbbaf9749 Monotone-Author: ludovic@Sophos.ca Monotone-Date: 2010-03-08T15:18:05 Monotone-Branch: ca.inverse.sogomaint-2.0.2
parent
13206f066d
commit
40c3cb74d0
13
ChangeLog
13
ChangeLog
|
@ -1,3 +1,16 @@
|
|||
2010-03-08 Ludovic Marcotte <lmarcotte@inverse.ca>
|
||||
|
||||
* Added SoObjects/SOGo/SOGoConstants.{h,m} - a new file
|
||||
that will hold eventually all SOGo constants.
|
||||
|
||||
* Reworked the authentication code to use a generic
|
||||
method for password authentication (LDAP + SQL).
|
||||
|
||||
* Added password change support
|
||||
|
||||
* Updated the SOPE patchset which now includes
|
||||
password-policy support.
|
||||
|
||||
2010-03-05 Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
|
||||
* SoObjects/SOGo/NSString+DAV.m (-davSetterName): enhanced so that
|
||||
|
|
|
@ -1,3 +1,311 @@
|
|||
Index: sope-ldap/NGLdap/NGLdapConnection.m
|
||||
===================================================================
|
||||
--- sope-ldap/NGLdap/NGLdapConnection.m (revision 1664)
|
||||
+++ sope-ldap/NGLdap/NGLdapConnection.m (working copy)
|
||||
@@ -26,7 +26,6 @@
|
||||
#include "NGLdapModification.h"
|
||||
#include "EOQualifier+LDAP.h"
|
||||
#include "common.h"
|
||||
-#include <ldap.h>
|
||||
|
||||
static BOOL LDAPDebugEnabled = NO;
|
||||
static BOOL LDAPInitialBindSpecific = NO;
|
||||
@@ -310,6 +309,295 @@
|
||||
return NO;
|
||||
}
|
||||
|
||||
+#ifdef LDAP_CONTROL_PASSWORDPOLICYREQUEST
|
||||
+- (BOOL) bindWithMethod: (NSString *) _method
|
||||
+ binddn: (NSString *) _login
|
||||
+ credentials: (NSString *) _cred
|
||||
+ perr: (LDAPPasswordPolicyError *) _perr
|
||||
+ expire: (int *) _expire
|
||||
+ grace: (int *) _grace
|
||||
+{
|
||||
+ LDAPControl **sctrlsp = NULL;
|
||||
+ LDAPControl *sctrls[2];
|
||||
+ LDAPControl sctrl[2];
|
||||
+ LDAPControl **ctrls;
|
||||
+ LDAPControl c, *ctrl;
|
||||
+ LDAPMessage *result = NULL;
|
||||
+
|
||||
+
|
||||
+ int err, msgid, rc;
|
||||
+ const char *l, *p;
|
||||
+ char *matched = NULL;
|
||||
+ char *info = NULL;
|
||||
+ char **refs = NULL;
|
||||
+ struct berval passwd = { 0, NULL };
|
||||
+
|
||||
+ l = (char *)[_login UTF8String];
|
||||
+ p = LDAPUseLatin1Creds
|
||||
+ ? (char *)[_cred cString]
|
||||
+ : (char *)[_cred UTF8String];
|
||||
+
|
||||
+ *_perr = -1;
|
||||
+ passwd.bv_val = p;
|
||||
+ passwd.bv_len = strlen(p);
|
||||
+
|
||||
+
|
||||
+ c.ldctl_oid = LDAP_CONTROL_PASSWORDPOLICYREQUEST;
|
||||
+ c.ldctl_value.bv_val = NULL;
|
||||
+ c.ldctl_value.bv_len = 0;
|
||||
+ c.ldctl_iscritical = 0;
|
||||
+ sctrl[0] = c;
|
||||
+ sctrls[0] = &sctrl[0];
|
||||
+ sctrls[1] = NULL;
|
||||
+
|
||||
+ sctrlsp = sctrls;
|
||||
+
|
||||
+ rc = ldap_sasl_bind(self->handle, l, LDAP_SASL_SIMPLE, &passwd, sctrlsp, NULL, &msgid);
|
||||
+
|
||||
+ if (msgid == -1 || rc != LDAP_SUCCESS)
|
||||
+ {
|
||||
+ [self logWithFormat: @"bind - ldap_sasl_bind call failed"];
|
||||
+ return NO;
|
||||
+ }
|
||||
+
|
||||
+ rc = ldap_result(self->handle, msgid, LDAP_MSG_ALL, NULL, &result);
|
||||
+
|
||||
+ if (rc == -1)
|
||||
+ {
|
||||
+ [self logWithFormat: @"bind - ldap_result call failed"];
|
||||
+ if (result) ldap_msgfree(result);
|
||||
+ return NO;
|
||||
+ }
|
||||
+
|
||||
+ [self logWithFormat: @"bind - ldap_result call result: %d", rc];
|
||||
+
|
||||
+ rc = ldap_parse_result(self->handle, result, &err, &matched, &info, &refs, &ctrls, 1);
|
||||
+
|
||||
+ if (rc != LDAP_SUCCESS)
|
||||
+ {
|
||||
+ [self logWithFormat: @"bind - ldap_parse_result call failed"];
|
||||
+ //if (result) ldap_msgfree(result); => causes a crash!?
|
||||
+ if (matched) ber_memfree(matched);
|
||||
+ if (info) ber_memfree(info);
|
||||
+ if (refs) ber_memvfree((void **)refs);
|
||||
+ return NO;
|
||||
+ }
|
||||
+
|
||||
+ if (err == LDAP_SUCCESS)
|
||||
+ self->flags.isBound = YES;
|
||||
+ else
|
||||
+ self->flags.isBound = NO;
|
||||
+
|
||||
+ // Even if we aren't bound to the server, we continue and we go get the
|
||||
+ // policy control
|
||||
+ if (ctrls)
|
||||
+ {
|
||||
+ ctrl = ldap_control_find(LDAP_CONTROL_PASSWORDPOLICYRESPONSE, ctrls, NULL);
|
||||
+ if (ctrl)
|
||||
+ {
|
||||
+ rc = ldap_parse_passwordpolicy_control(self->handle, ctrl, _expire, _grace, _perr);
|
||||
+
|
||||
+ if (rc == LDAP_SUCCESS)
|
||||
+ {
|
||||
+ [self logWithFormat: @"bind - policy values: %d %d %d - bound: %d", *_expire, *_grace, *_perr, self->flags.isBound];
|
||||
+ }
|
||||
+ else
|
||||
+ [self logWithFormat: @"bind - ldap_parse_passwordpolicy call failed"];
|
||||
+ }
|
||||
+ else
|
||||
+ [self logWithFormat: @"bind - ldap_control_find call failed"];
|
||||
+
|
||||
+ ldap_controls_free(ctrls);
|
||||
+ }
|
||||
+ else
|
||||
+ {
|
||||
+ [self logWithFormat: @"bind - ldap_parse_result - ctrls is NULL"];
|
||||
+ }
|
||||
+
|
||||
+ return self->flags.isBound;
|
||||
+}
|
||||
+
|
||||
+//
|
||||
+// No need to bind prior to calling this method. In fact,
|
||||
+// if a bind() was issued prior calling this method, it
|
||||
+// will fail.
|
||||
+//
|
||||
+- (BOOL) changePasswordAtDn: (NSString *) _dn
|
||||
+ oldPassword: (NSString *) _oldPassword
|
||||
+ newPassword: (NSString *) _newPassword
|
||||
+ perr: (LDAPPasswordPolicyError *) _perr
|
||||
+
|
||||
+{
|
||||
+ const char *user, *p;
|
||||
+ int rc;
|
||||
+
|
||||
+ *_perr = -1;
|
||||
+
|
||||
+ user = (char *)[_dn UTF8String];
|
||||
+ p = LDAPUseLatin1Creds ? (char *)[_oldPassword cString] : (char *)[_oldPassword UTF8String];
|
||||
+
|
||||
+ if (!self->flags.isBound)
|
||||
+ {
|
||||
+ rc = ldap_simple_bind_s(self->handle, user, p);
|
||||
+
|
||||
+ if (rc == LDAP_SUCCESS)
|
||||
+ {
|
||||
+ struct berval newpw = { 0, NULL };
|
||||
+ struct berval oldpw = { 0, NULL };
|
||||
+ struct berval bv = {0, NULL};
|
||||
+ struct berval *retdata = NULL;
|
||||
+
|
||||
+ LDAPControl *sctrls[2];
|
||||
+ LDAPControl **ctrls;
|
||||
+ LDAPControl sctrl[2];
|
||||
+ LDAPControl c, *ctrl;
|
||||
+ LDAPMessage *result;
|
||||
+
|
||||
+ BerElement *ber = NULL;
|
||||
+
|
||||
+ char *matcheddn = NULL, *retoid = NULL, *text = NULL, **refs = NULL;
|
||||
+ int idd, grace, expire, code;
|
||||
+
|
||||
+ self->flags.isBound = YES;
|
||||
+ code = LDAP_OTHER;
|
||||
+
|
||||
+ newpw.bv_val = LDAPUseLatin1Creds ? (char *)[_newPassword cString] : (char *)[_newPassword UTF8String];
|
||||
+ newpw.bv_len = strlen(newpw.bv_val);
|
||||
+
|
||||
+ oldpw.bv_val = p;
|
||||
+ oldpw.bv_len = strlen(p);
|
||||
+
|
||||
+ ber = ber_alloc_t(LBER_USE_DER);
|
||||
+
|
||||
+ if (ber == NULL)
|
||||
+ return NO;
|
||||
+
|
||||
+ ber_printf(ber, "{" /*}*/ );
|
||||
+ ber_printf(ber, "ts", LDAP_TAG_EXOP_MODIFY_PASSWD_ID, user);
|
||||
+ ber_printf(ber, "tO", LDAP_TAG_EXOP_MODIFY_PASSWD_OLD, &oldpw);
|
||||
+ ber_printf(ber, "tO", LDAP_TAG_EXOP_MODIFY_PASSWD_NEW, &newpw);
|
||||
+ ber_printf(ber, /*{*/ "N}" );
|
||||
+
|
||||
+ rc = ber_flatten2(ber, &bv, 0 );
|
||||
+
|
||||
+ if (rc < 0)
|
||||
+ {
|
||||
+ [self logWithFormat: @"change password - ber_flatten2 call failed"];
|
||||
+ ber_free(ber, 1);
|
||||
+ return NO;
|
||||
+ }
|
||||
+
|
||||
+ // Everything is alright...
|
||||
+ *_perr = -1;
|
||||
+
|
||||
+ c.ldctl_oid = LDAP_CONTROL_PASSWORDPOLICYREQUEST;
|
||||
+ c.ldctl_value.bv_val = NULL;
|
||||
+ c.ldctl_value.bv_len = 0;
|
||||
+ c.ldctl_iscritical = 0;
|
||||
+ sctrl[0] = c;
|
||||
+ sctrls[0] = &sctrl[0];
|
||||
+ sctrls[1] = NULL;
|
||||
+
|
||||
+ rc = ldap_set_option(self->handle, LDAP_OPT_SERVER_CONTROLS, sctrls);
|
||||
+
|
||||
+ if (rc != LDAP_OPT_SUCCESS)
|
||||
+ {
|
||||
+ [self logWithFormat: @"change password - ldap_set_option call failed"];
|
||||
+ ber_free(ber, 1);
|
||||
+ return NO;
|
||||
+ }
|
||||
+
|
||||
+ rc = ldap_extended_operation(self->handle,
|
||||
+ LDAP_EXOP_MODIFY_PASSWD, &bv,
|
||||
+ NULL, NULL, &idd);
|
||||
+
|
||||
+ ber_free(ber, 1);
|
||||
+
|
||||
+ if (rc != LDAP_SUCCESS )
|
||||
+ {
|
||||
+ [self logWithFormat: @"change password - ldap_extended_operation call failed"];
|
||||
+ return NO;
|
||||
+ }
|
||||
+
|
||||
+ rc = ldap_result(self->handle, LDAP_RES_ANY, LDAP_MSG_ALL, NULL, &result);
|
||||
+
|
||||
+ if (rc < 0)
|
||||
+ {
|
||||
+ [self logWithFormat: @"change password - ldap_result call failed"];
|
||||
+ return NO;
|
||||
+ }
|
||||
+
|
||||
+ rc = ldap_parse_result(self->handle, result, &code, &matcheddn, &text, &refs, &ctrls, 0 );
|
||||
+
|
||||
+ if (rc != LDAP_SUCCESS)
|
||||
+ {
|
||||
+ [self logWithFormat: @"change password - ldap_parse_result call failed, rc = %d, code = %d, matcheddn = %s, text = %s", rc, code, matcheddn, text];
|
||||
+ ber_memfree(text);
|
||||
+ ber_memfree(matcheddn);
|
||||
+ ber_memvfree((void **) refs);
|
||||
+ free(ctrls);
|
||||
+ return NO;
|
||||
+ }
|
||||
+
|
||||
+ rc = ldap_parse_extended_result(self->handle, result, &retoid, &retdata, 1);
|
||||
+ if (rc != LDAP_SUCCESS)
|
||||
+ {
|
||||
+ [self logWithFormat: @"change password - ldap_parse_extended result call failed"];
|
||||
+ ber_memfree(text);
|
||||
+ ber_memfree(matcheddn);
|
||||
+ ber_memvfree((void **) refs);
|
||||
+ ber_memfree(retoid);
|
||||
+ ber_bvfree(retdata);
|
||||
+ free(ctrls);
|
||||
+ return NO;
|
||||
+ }
|
||||
+
|
||||
+ ctrl = ldap_control_find(LDAP_CONTROL_PASSWORDPOLICYRESPONSE, ctrls, NULL);
|
||||
+
|
||||
+ if (ctrl)
|
||||
+ {
|
||||
+ rc = ldap_parse_passwordpolicy_control(self->handle, ctrl, &expire, &grace, _perr);
|
||||
+
|
||||
+ if (rc == LDAP_SUCCESS && *_perr == PP_noError)
|
||||
+ {
|
||||
+ [self logWithFormat: @"change password - policy values: %d %d %d", expire, grace, *_perr];
|
||||
+ }
|
||||
+ else
|
||||
+ {
|
||||
+ [self logWithFormat: @"change password - ldap_parse_passwordpolicy call failed or error during password change: %d", *_perr];
|
||||
+ ber_memfree(text);
|
||||
+ ber_memfree(matcheddn);
|
||||
+ ber_memvfree((void **) refs);
|
||||
+ ber_memfree(retoid);
|
||||
+ ber_bvfree(retdata);
|
||||
+ free(ctrls);
|
||||
+ return NO;
|
||||
+ }
|
||||
+ }
|
||||
+ else
|
||||
+ {
|
||||
+ // Ending up here doesn't mean that things failed. It could simply be caused by the
|
||||
+ // fact that the password change was a success but no policy control object
|
||||
+ // could be found.
|
||||
+ [self logWithFormat: @"change password - ldap_control_find call failed"];
|
||||
+ }
|
||||
+
|
||||
+ ber_memfree(text);
|
||||
+ ber_memfree(matcheddn);
|
||||
+ ber_memvfree((void **) refs);
|
||||
+ ber_memfree(retoid);
|
||||
+ ber_bvfree(retdata);
|
||||
+ free(ctrls);
|
||||
+
|
||||
+ return YES;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return NO;
|
||||
+}
|
||||
+
|
||||
+#endif
|
||||
+
|
||||
/* running queries */
|
||||
|
||||
- (void)setQueryTimeLimit:(NSTimeInterval)_timeLimit {
|
||||
Index: sope-ldap/NGLdap/NGLdapEntry.m
|
||||
===================================================================
|
||||
--- sope-ldap/NGLdap/NGLdapEntry.m (revision 1664)
|
||||
|
@ -25,7 +333,12 @@ Index: sope-ldap/NGLdap/ChangeLog
|
|||
===================================================================
|
||||
--- sope-ldap/NGLdap/ChangeLog (revision 1664)
|
||||
+++ sope-ldap/NGLdap/ChangeLog (working copy)
|
||||
@@ -1,3 +1,8 @@
|
||||
@@ -1,3 +1,13 @@
|
||||
+2010-03-08 Ludovic Marcotte <lmarcotte@inverse.ca>
|
||||
+
|
||||
+ * Added password policy support when binding to the
|
||||
+ LDAP server or when changing passwords.
|
||||
+
|
||||
+2009-08-13 Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
+
|
||||
+ * NGLdapEntry.m (-attributeWithName:): attribute names are now
|
||||
|
@ -34,6 +347,41 @@ Index: sope-ldap/NGLdap/ChangeLog
|
|||
2009-04-02 Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
|
||||
* NGLdapConnection.m (useSSL,startTLS): new method enabling
|
||||
Index: sope-ldap/NGLdap/NGLdapConnection.h
|
||||
===================================================================
|
||||
--- sope-ldap/NGLdap/NGLdapConnection.h (revision 1664)
|
||||
+++ sope-ldap/NGLdap/NGLdapConnection.h (working copy)
|
||||
@@ -25,6 +25,9 @@
|
||||
#import <Foundation/NSObject.h>
|
||||
#import <Foundation/NSDate.h>
|
||||
|
||||
+#define LDAP_DEPRECATED 1
|
||||
+#include <ldap.h>
|
||||
+
|
||||
@class NSString, NSArray, NSEnumerator;
|
||||
@class EOQualifier;
|
||||
@class NGLdapEntry;
|
||||
@@ -65,6 +68,20 @@
|
||||
- (BOOL)bindWithMethod:(NSString *)_method
|
||||
binddn:(NSString *)_login credentials:(NSString *)_cred;
|
||||
|
||||
+#ifdef LDAP_CONTROL_PASSWORDPOLICYREQUEST
|
||||
+- (BOOL) bindWithMethod: (NSString *) _method
|
||||
+ binddn: (NSString *) _login
|
||||
+ credentials: (NSString *) _cred
|
||||
+ perr: (LDAPPasswordPolicyError *) _perr
|
||||
+ expire: (int *) _expire
|
||||
+ grace: (int *) _grace;
|
||||
+
|
||||
+- (BOOL) changePasswordAtDn: (NSString *) _dn
|
||||
+ oldPassword: (NSString *) _oldPassword
|
||||
+ newPassword: (NSString *) _newPassword
|
||||
+ perr: (LDAPPasswordPolicyError *) _perr;
|
||||
+#endif
|
||||
+
|
||||
/* query parameters */
|
||||
|
||||
- (void)setQueryTimeLimit:(NSTimeInterval)_timeLimit;
|
||||
Index: sope-gdl1/PostgreSQL/PostgreSQL72Channel.m
|
||||
===================================================================
|
||||
--- sope-gdl1/PostgreSQL/PostgreSQL72Channel.m (revision 1664)
|
||||
|
|
|
@ -19,6 +19,7 @@ SOGo_HEADER_FILES = \
|
|||
SOGoProductLoader.h \
|
||||
\
|
||||
SOGoCache.h \
|
||||
SOGoConstants.h \
|
||||
SOGoObject.h \
|
||||
SOGoContentObject.h \
|
||||
SOGoFolder.h \
|
||||
|
@ -80,6 +81,7 @@ SOGo_OBJC_FILES = \
|
|||
SOGoProductLoader.m \
|
||||
\
|
||||
SOGoCache.m \
|
||||
SOGoConstants.m \
|
||||
SOGoObject.m \
|
||||
SOGoContentObject.m \
|
||||
SOGoFolder.m \
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/* LDAPSource.h - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2007-2009 Inverse inc.
|
||||
* Copyright (C) 2007-2010 Inverse inc.
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
*
|
||||
|
@ -26,6 +26,7 @@
|
|||
#import <Foundation/NSObject.h>
|
||||
|
||||
#include "SOGoSource.h"
|
||||
#include "SOGoConstants.h"
|
||||
|
||||
@class NSDictionary;
|
||||
@class NSString;
|
||||
|
@ -60,6 +61,8 @@
|
|||
NSDictionary *modulesConstraints;
|
||||
|
||||
NSMutableArray *searchAttributes;
|
||||
|
||||
BOOL passwordPolicy;
|
||||
}
|
||||
|
||||
- (void) setBindDN: (NSString *) newBindDN
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#import <NGLdap/NGLdapConnection.h>
|
||||
#import <NGLdap/NGLdapAttribute.h>
|
||||
#import <NGLdap/NGLdapEntry.h>
|
||||
#import <NGLdap/NGLdapModification.h>
|
||||
|
||||
#import "NSArray+Utilities.h"
|
||||
#import "NSString+Utilities.h"
|
||||
|
@ -157,6 +158,7 @@ static NSArray *commonSearchFields;
|
|||
_filter = nil;
|
||||
|
||||
searchAttributes = nil;
|
||||
passwordPolicy = NO;
|
||||
}
|
||||
|
||||
return self;
|
||||
|
@ -239,6 +241,9 @@ static NSArray *commonSearchFields;
|
|||
ASSIGN (_scope, ([udSource objectForKey: @"scope"]
|
||||
? [udSource objectForKey: @"scope"]
|
||||
: (id)@"sub"));
|
||||
|
||||
if ([udSource objectForKey: @"passwordPolicy"])
|
||||
passwordPolicy = [[udSource objectForKey: @"passwordPolicy"] boolValue];
|
||||
}
|
||||
|
||||
return self;
|
||||
|
@ -414,16 +419,19 @@ static NSArray *commonSearchFields;
|
|||
return userDN;
|
||||
}
|
||||
|
||||
- (BOOL) checkLogin: (NSString *) loginToCheck
|
||||
andPassword: (NSString *) passwordToCheck
|
||||
- (BOOL) checkLogin: (NSString *) _login
|
||||
password: (NSString *) _pwd
|
||||
perr: (SOGoPasswordPolicyError *) _perr
|
||||
expire: (int *) _expire
|
||||
grace: (int *) _grace
|
||||
{
|
||||
BOOL didBind;
|
||||
NSString *userDN;
|
||||
NGLdapConnection *bindConnection;
|
||||
NSString *userDN;
|
||||
BOOL didBind;
|
||||
|
||||
didBind = NO;
|
||||
|
||||
if ([loginToCheck length] > 0)
|
||||
if ([_login length] > 0)
|
||||
{
|
||||
bindConnection = [[NGLdapConnection alloc] initWithHostName: hostname
|
||||
port: port];
|
||||
|
@ -432,16 +440,24 @@ static NSArray *commonSearchFields;
|
|||
if (queryTimeout > 0)
|
||||
[bindConnection setQueryTimeLimit: queryTimeout];
|
||||
if (bindFields)
|
||||
userDN = [self _fetchUserDNForLogin: loginToCheck];
|
||||
userDN = [self _fetchUserDNForLogin: _login];
|
||||
else
|
||||
userDN = [NSString stringWithFormat: @"%@=%@,%@",
|
||||
IDField, loginToCheck, baseDN];
|
||||
IDField, _login, baseDN];
|
||||
if (userDN)
|
||||
{
|
||||
NS_DURING
|
||||
if (!passwordPolicy)
|
||||
didBind = [bindConnection bindWithMethod: @"simple"
|
||||
binddn: userDN
|
||||
credentials: passwordToCheck];
|
||||
credentials: _pwd];
|
||||
else
|
||||
didBind = [bindConnection bindWithMethod: @"simple"
|
||||
binddn: userDN
|
||||
credentials: _pwd
|
||||
perr: (void *)_perr
|
||||
expire: _expire
|
||||
grace: _grace];
|
||||
NS_HANDLER
|
||||
;
|
||||
NS_ENDHANDLER
|
||||
|
@ -454,6 +470,75 @@ static NSArray *commonSearchFields;
|
|||
return didBind;
|
||||
}
|
||||
|
||||
- (BOOL) changePasswordForLogin: (NSString *) login
|
||||
oldPassword: (NSString *) oldPassword
|
||||
newPassword: (NSString *) newPassword
|
||||
perr: (SOGoPasswordPolicyError *) perr
|
||||
|
||||
{
|
||||
NGLdapConnection *bindConnection;
|
||||
NSString *userDN;
|
||||
BOOL didChange;
|
||||
|
||||
didChange = NO;
|
||||
|
||||
if ([login length] > 0)
|
||||
{
|
||||
bindConnection = [[NGLdapConnection alloc] initWithHostName: hostname
|
||||
port: port];
|
||||
if (![encryption length] || [self _setupEncryption: bindConnection])
|
||||
{
|
||||
if (queryTimeout > 0)
|
||||
[bindConnection setQueryTimeLimit: queryTimeout];
|
||||
if (bindFields)
|
||||
userDN = [self _fetchUserDNForLogin: login];
|
||||
else
|
||||
userDN = [NSString stringWithFormat: @"%@=%@,%@",
|
||||
IDField, login, baseDN];
|
||||
if (userDN)
|
||||
{
|
||||
NS_DURING
|
||||
if (!passwordPolicy)
|
||||
{
|
||||
// We don't use a password policy - we simply use
|
||||
// a modify-op to change the password
|
||||
NGLdapModification *mod;
|
||||
NGLdapAttribute *attr;
|
||||
NSArray *changes;
|
||||
|
||||
attr = [[NGLdapAttribute alloc] initWithAttributeName: @"userPassword"];
|
||||
[attr addStringValue: newPassword];
|
||||
|
||||
mod = [NGLdapModification replaceModification: attr];
|
||||
changes = [NSArray arrayWithObject: mod];
|
||||
perr = PolicyNoError;
|
||||
|
||||
if ([bindConnection bindWithMethod: @"simple"
|
||||
binddn: userDN
|
||||
credentials: oldPassword])
|
||||
didChange = [bindConnection modifyEntryWithDN: userDN
|
||||
changes: changes];
|
||||
else
|
||||
didChange = NO;
|
||||
}
|
||||
else
|
||||
didChange = [bindConnection changePasswordAtDn: userDN
|
||||
oldPassword: oldPassword
|
||||
newPassword: newPassword
|
||||
perr: (void *)perr];
|
||||
NS_HANDLER
|
||||
;
|
||||
NS_ENDHANDLER
|
||||
;
|
||||
}
|
||||
}
|
||||
[bindConnection release];
|
||||
}
|
||||
|
||||
return didChange;
|
||||
}
|
||||
|
||||
|
||||
/* contact management */
|
||||
- (EOQualifier *) _qualifierForFilter: (NSString *) filter
|
||||
{
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/* SOGoAuthenticator.h - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2007 Inverse inc.
|
||||
* Copyright (C) 2007-2010 Inverse inc.
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
*
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
/* SOGoConstants.h - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2010 Inverse inc.
|
||||
*
|
||||
* Author: Ludovic Marcotte <lmarcotte@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 _SOGOCONSTANTS_H_
|
||||
#define _SOGOCONSTANTS_H_
|
||||
|
||||
// This is a perfect copy of the OpenLDAP's
|
||||
// LDAPPasswordPolicyError enum. We redeclare it
|
||||
// so that we always include the ppolicy code
|
||||
// within SOGo.
|
||||
typedef enum
|
||||
{
|
||||
PolicyPasswordExpired = 0,
|
||||
PolicyAccountLocked = 1,
|
||||
PolicyChangeAfterReset = 2,
|
||||
PolicyPasswordModNotAllowed = 3,
|
||||
PolicyMustSupplyOldPassword = 4,
|
||||
PolicyInsufficientPasswordQuality = 5,
|
||||
PolicyPasswordTooShort = 6,
|
||||
PolicyPasswordTooYoung = 7,
|
||||
PolicyPasswordInHistory = 8,
|
||||
PolicyNoError = 65535,
|
||||
} SOGoPasswordPolicyError;
|
||||
|
||||
// Domain defaults
|
||||
extern NSString *SOGoPasswordChangeEnabled;
|
||||
extern NSString *SOGoPasswordPolicyEnabled;
|
||||
|
||||
#endif /* _SOGOCONSTANTS_H_ */
|
|
@ -0,0 +1,27 @@
|
|||
/* SOGoConstants.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2010 Inverse inc.
|
||||
*
|
||||
* Author: Ludovic Marcotte <lmarcotte@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/NSString.h>
|
||||
|
||||
// LDAP Password Policy Constants
|
||||
NSString* SOGoPasswordChangeEnabled = @"SOGoPasswordChangeEnabled";
|
||||
NSString* SOGoPasswordPolicyEnabled = @"SOGoPasswordPolicyEnabled";
|
|
@ -27,6 +27,7 @@
|
|||
#import <NGObjWeb/WOResponse.h>
|
||||
#import <NGExtensions/NSObject+Logs.h>
|
||||
|
||||
#import "SOGoConstants.h"
|
||||
#import "SOGoUserManager.h"
|
||||
#import "SOGoPermissions.h"
|
||||
#import "SOGoUser.h"
|
||||
|
@ -48,8 +49,22 @@
|
|||
- (BOOL) checkLogin: (NSString *) _login
|
||||
password: (NSString *) _pwd
|
||||
{
|
||||
return [[SOGoUserManager sharedUserManager] checkLogin: _login
|
||||
andPassword: _pwd];
|
||||
SOGoPasswordPolicyError perr;
|
||||
int expire, grace;
|
||||
BOOL b;
|
||||
|
||||
perr = PolicyNoError;
|
||||
|
||||
b = [[SOGoUserManager sharedUserManager] checkLogin: _login
|
||||
password: _pwd
|
||||
perr: &perr
|
||||
expire: &expire
|
||||
grace: &grace];
|
||||
|
||||
if (b && perr == PolicyNoError)
|
||||
return YES;
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSString *) passwordInContext: (WOContext *) context
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/* SOGoDomainDefaults.h - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2009 Inverse inc.
|
||||
* Copyright (C) 2009-2010 Inverse inc.
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
*
|
||||
|
@ -46,6 +46,8 @@
|
|||
- (BOOL) sieveScriptsEnabled;
|
||||
- (BOOL) forwardEnabled;
|
||||
- (BOOL) vacationEnabled;
|
||||
- (BOOL) passwordChangeEnabled;
|
||||
- (BOOL) passwordPolicyEnabled;
|
||||
- (NSString *) mailingMechanism;
|
||||
- (NSString *) smtpServer;
|
||||
- (NSString *) mailSpoolPath;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/* SOGoDomainDefaults.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2009 Inverse inc.
|
||||
* Copyright (C) 2009-2010 Inverse inc.
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
*
|
||||
|
@ -30,6 +30,7 @@
|
|||
#import "SOGoSystemDefaults.h"
|
||||
|
||||
#import "SOGoDomainDefaults.h"
|
||||
#import "SOGoConstants.h"
|
||||
|
||||
@implementation SOGoDomainDefaults
|
||||
|
||||
|
@ -170,6 +171,16 @@
|
|||
return [self boolForKey: @"SOGoVacationEnabled"];
|
||||
}
|
||||
|
||||
- (BOOL) passwordChangeEnabled
|
||||
{
|
||||
return [self boolForKey: SOGoPasswordChangeEnabled];
|
||||
}
|
||||
|
||||
- (BOOL) passwordPolicyEnabled
|
||||
{
|
||||
return [self boolForKey: SOGoPasswordPolicyEnabled];
|
||||
}
|
||||
|
||||
- (NSString *) mailingMechanism
|
||||
{
|
||||
NSString *mailingMechanism;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/* SOGoSource.h - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2009 Inverse inc.
|
||||
* Copyright (C) 2009-2010 Inverse inc.
|
||||
*
|
||||
* Author: Ludovic Marcotte <lmarcotte@inverse.ca>
|
||||
*
|
||||
|
@ -25,6 +25,8 @@
|
|||
|
||||
#import <Foundation/NSObject.h>
|
||||
|
||||
#import "SOGoConstants.h"
|
||||
|
||||
@class NSDictionary;
|
||||
@class NSString;
|
||||
|
||||
|
@ -38,8 +40,16 @@
|
|||
|
||||
- (NSString *) domain;
|
||||
|
||||
- (BOOL) checkLogin: (NSString *) login
|
||||
andPassword: (NSString *) password;
|
||||
- (BOOL) checkLogin: (NSString *) _login
|
||||
password: (NSString *) _pwd
|
||||
perr: (SOGoPasswordPolicyError *) _perr
|
||||
expire: (int *) _expire
|
||||
grace: (int *) _grace;
|
||||
|
||||
- (BOOL) changePasswordForLogin: (NSString *) login
|
||||
oldPassword: (NSString *) oldPassword
|
||||
newPassword: (NSString *) newPassword
|
||||
perr: (SOGoPasswordPolicyError *) perr;
|
||||
|
||||
- (NSDictionary *) lookupContactEntry: (NSString *) theID;
|
||||
- (NSDictionary *) lookupContactEntryWithUIDorEmail: (NSString *) entryID;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/* SOGoSystemDefaults.h - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2009 Inverse inc.
|
||||
* Copyright (C) 2009-2010 Inverse inc.
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
*
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/* SOGoUserManager.h - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2007-2009 Inverse inc.
|
||||
* Copyright (C) 2007-2010 Inverse inc.
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
*
|
||||
|
@ -25,6 +25,8 @@
|
|||
|
||||
#import <Foundation/NSObject.h>
|
||||
|
||||
#import "SOGoConstants.h"
|
||||
|
||||
@class NSDictionary;
|
||||
@class NSMutableDictionary;
|
||||
@class NSString;
|
||||
|
@ -75,9 +77,16 @@
|
|||
- (NSString *) getUIDForEmail: (NSString *) email;
|
||||
- (NSString *) getLoginForDN: (NSString *) theDN;
|
||||
|
||||
- (BOOL) checkLogin: (NSString *) login
|
||||
andPassword: (NSString *) password;
|
||||
- (BOOL) checkLogin: (NSString *) _login
|
||||
password: (NSString *) _pwd
|
||||
perr: (SOGoPasswordPolicyError *) _perr
|
||||
expire: (int *) _expire
|
||||
grace: (int *) _grace;
|
||||
|
||||
- (BOOL) changePasswordForLogin: (NSString *) login
|
||||
oldPassword: (NSString *) oldPassword
|
||||
newPassword: (NSString *) newPassword
|
||||
perr: (SOGoPasswordPolicyError *) perr;
|
||||
@end
|
||||
|
||||
#endif /* SOGOUSERMANAGER_H */
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/* SOGoUserManager.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2007-2009 Inverse inc.
|
||||
* Copyright (C) 2007-2010 Inverse inc.
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
*
|
||||
|
@ -36,6 +36,7 @@
|
|||
#import "SOGoSystemDefaults.h"
|
||||
#import "SOGoUserManager.h"
|
||||
#import "SOGoCache.h"
|
||||
#import "SOGoConstants.h"
|
||||
#import "SOGoSource.h"
|
||||
|
||||
@implementation SOGoUserManagerRegistry
|
||||
|
@ -89,7 +90,7 @@
|
|||
{
|
||||
NSString *sourceID, *value, *type;
|
||||
NSMutableDictionary *metadata;
|
||||
NSObject <SOGoSource> *ldapSource;
|
||||
NSObject <SOGoSource> *sogoSource;
|
||||
BOOL isAddressBook;
|
||||
Class c;
|
||||
|
||||
|
@ -97,10 +98,10 @@
|
|||
if ([sourceID length] > 0)
|
||||
{
|
||||
type = [[udSource objectForKey: @"type"] lowercaseString];
|
||||
c = NSClassFromString ([_registry sourceClassForType: type]);
|
||||
ldapSource = [c sourceFromUDSource: udSource inDomain: domain];
|
||||
c = NSClassFromString([_registry sourceClassForType: type]);
|
||||
sogoSource = [c sourceFromUDSource: udSource inDomain: domain];
|
||||
if (sourceID)
|
||||
[_sources setObject: ldapSource forKey: sourceID];
|
||||
[_sources setObject: sogoSource forKey: sourceID];
|
||||
else
|
||||
[self errorWithFormat: @"id field missing in an user source,"
|
||||
@" check the SOGoUserSources defaults"];
|
||||
|
@ -335,16 +336,43 @@
|
|||
{
|
||||
NSDictionary *contactInfos;
|
||||
|
||||
// NSLog (@"getUIDForEmail: %@", email);
|
||||
contactInfos = [self contactInfosForUserWithUIDorEmail: email];
|
||||
|
||||
return [contactInfos objectForKey: @"c_uid"];
|
||||
}
|
||||
|
||||
- (BOOL) _sourceChangePasswordForLogin: (NSString *) login
|
||||
oldPassword: (NSString *) oldPassword
|
||||
newPassword: (NSString *) newPassword
|
||||
perr: (SOGoPasswordPolicyError *) perr
|
||||
{
|
||||
NSObject <SOGoSource> *sogoSource;
|
||||
NSEnumerator *authIDs;
|
||||
NSString *currentID;
|
||||
BOOL didChange;
|
||||
|
||||
didChange = NO;
|
||||
|
||||
authIDs = [[self authenticationSourceIDsInDomain: nil] objectEnumerator];
|
||||
while (!didChange && (currentID = [authIDs nextObject]))
|
||||
{
|
||||
sogoSource = [_sources objectForKey: currentID];
|
||||
didChange = [sogoSource changePasswordForLogin: login
|
||||
oldPassword: oldPassword
|
||||
newPassword: newPassword
|
||||
perr: perr];
|
||||
}
|
||||
|
||||
return didChange;
|
||||
}
|
||||
|
||||
- (BOOL) _sourceCheckLogin: (NSString *) login
|
||||
andPassword: (NSString *) password
|
||||
perr: (SOGoPasswordPolicyError *) perr
|
||||
expire: (int *) expire
|
||||
grace: (int *) grace
|
||||
{
|
||||
NSObject <SOGoSource> *ldapSource;
|
||||
NSObject <SOGoSource> *sogoSource;
|
||||
NSEnumerator *authIDs;
|
||||
NSString *currentID;
|
||||
BOOL checkOK;
|
||||
|
@ -354,26 +382,38 @@
|
|||
authIDs = [[self authenticationSourceIDsInDomain: nil] objectEnumerator];
|
||||
while (!checkOK && (currentID = [authIDs nextObject]))
|
||||
{
|
||||
ldapSource = [_sources objectForKey: currentID];
|
||||
checkOK = [ldapSource checkLogin: login andPassword: password];
|
||||
sogoSource = [_sources objectForKey: currentID];
|
||||
checkOK = [sogoSource checkLogin: login
|
||||
password: password
|
||||
perr: perr
|
||||
expire: expire
|
||||
grace: grace];
|
||||
}
|
||||
|
||||
|
||||
return checkOK;
|
||||
}
|
||||
|
||||
- (BOOL) checkLogin: (NSString *) login
|
||||
andPassword: (NSString *) password
|
||||
- (BOOL) checkLogin: (NSString *) _login
|
||||
password: (NSString *) _pwd
|
||||
perr: (SOGoPasswordPolicyError *) _perr
|
||||
expire: (int *) _expire
|
||||
grace: (int *) _grace
|
||||
{
|
||||
NSMutableDictionary *currentUser;
|
||||
NSString *dictPassword, *jsonUser;
|
||||
NSMutableDictionary *currentUser;
|
||||
BOOL checkOK;
|
||||
|
||||
jsonUser = [[SOGoCache sharedCache] userAttributesForLogin: login];
|
||||
jsonUser = [[SOGoCache sharedCache] userAttributesForLogin: _login];
|
||||
currentUser = [NSMutableDictionary dictionaryWithJSONString: jsonUser];
|
||||
dictPassword = [currentUser objectForKey: @"password"];
|
||||
if (currentUser && dictPassword)
|
||||
checkOK = ([dictPassword isEqualToString: password]);
|
||||
else if ([self _sourceCheckLogin: login andPassword: password])
|
||||
checkOK = ([dictPassword isEqualToString: _pwd]);
|
||||
else if ([self _sourceCheckLogin: _login
|
||||
andPassword: _pwd
|
||||
perr: _perr
|
||||
expire: _expire
|
||||
grace: _grace])
|
||||
{
|
||||
checkOK = YES;
|
||||
if (!currentUser)
|
||||
|
@ -386,10 +426,10 @@
|
|||
// set the password and recache the entry, the password would never be
|
||||
// cached for the user unless its entry expires from memcached's
|
||||
// internal cache.
|
||||
[currentUser setObject: password forKey: @"password"];
|
||||
[currentUser setObject: _pwd forKey: @"password"];
|
||||
[[SOGoCache sharedCache]
|
||||
setUserAttributes: [currentUser jsonStringValue]
|
||||
forLogin: login];
|
||||
forLogin: _login];
|
||||
}
|
||||
else
|
||||
checkOK = NO;
|
||||
|
@ -397,6 +437,47 @@
|
|||
return checkOK;
|
||||
}
|
||||
|
||||
- (BOOL) changePasswordForLogin: (NSString *) login
|
||||
oldPassword: (NSString *) oldPassword
|
||||
newPassword: (NSString *) newPassword
|
||||
perr: (SOGoPasswordPolicyError *) perr
|
||||
{
|
||||
NSString *dictPassword, *jsonUser;
|
||||
NSMutableDictionary *currentUser;
|
||||
BOOL didChange;
|
||||
|
||||
jsonUser = [[SOGoCache sharedCache] userAttributesForLogin: login];
|
||||
currentUser = [NSMutableDictionary dictionaryWithJSONString: jsonUser];
|
||||
dictPassword = [currentUser objectForKey: @"password"];
|
||||
|
||||
if ([self _sourceChangePasswordForLogin: login
|
||||
oldPassword: oldPassword
|
||||
newPassword: newPassword
|
||||
perr: perr])
|
||||
{
|
||||
didChange = YES;
|
||||
|
||||
if (!currentUser)
|
||||
{
|
||||
currentUser = [NSMutableDictionary dictionary];
|
||||
}
|
||||
|
||||
// It's important to cache the password here as we might have cached the
|
||||
// user's entry in -contactInfosForUserWithUIDorEmail: and if we don't
|
||||
// set the password and recache the entry, the password would never be
|
||||
// cached for the user unless its entry expires from memcached's
|
||||
// internal cache.
|
||||
[currentUser setObject: newPassword forKey: @"password"];
|
||||
[[SOGoCache sharedCache]
|
||||
setUserAttributes: [currentUser jsonStringValue]
|
||||
forLogin: login];
|
||||
}
|
||||
else
|
||||
didChange = NO;
|
||||
|
||||
return didChange;
|
||||
}
|
||||
|
||||
- (void) _fillContactMailRecords: (NSMutableDictionary *) contact
|
||||
{
|
||||
NSString *uid, *domain, *systemEmail;
|
||||
|
@ -424,8 +505,8 @@
|
|||
{
|
||||
NSMutableArray *emails;
|
||||
NSDictionary *userEntry;
|
||||
NSEnumerator *ldapSources;
|
||||
LDAPSource *currentSource;
|
||||
NSEnumerator *sogoSources;
|
||||
NSObject <SOGoDNSource> *currentSource;
|
||||
NSString *sourceID, *cn, *c_domain, *c_uid, *c_imaphostname;
|
||||
NSArray *c_emails;
|
||||
BOOL access;
|
||||
|
@ -441,9 +522,9 @@
|
|||
[currentUser setObject: [NSNumber numberWithBool: YES]
|
||||
forKey: @"MailAccess"];
|
||||
|
||||
ldapSources = [[self authenticationSourceIDsInDomain: nil]
|
||||
sogoSources = [[self authenticationSourceIDsInDomain: nil]
|
||||
objectEnumerator];
|
||||
while ((sourceID = [ldapSources nextObject]))
|
||||
while ((sourceID = [sogoSources nextObject]))
|
||||
{
|
||||
currentSource = [_sources objectForKey: sourceID];
|
||||
userEntry = [currentSource lookupContactEntryWithUIDorEmail: uid];
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
/* SOGoWebAuthenticator.h - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2007 Inverse inc.
|
||||
* Copyright (C) 2007-2010 Inverse inc.
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
* Ludovic Marcotte <lmarcotte@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
|
||||
|
@ -24,7 +25,9 @@
|
|||
#define _SOGOWEBAUTHENTICATOR_H_
|
||||
|
||||
#import <NGObjWeb/SoCookieAuthenticator.h>
|
||||
|
||||
#import "SOGoAuthenticator.h"
|
||||
#import "SOGoConstants.h"
|
||||
|
||||
@class NSString;
|
||||
|
||||
|
@ -34,6 +37,12 @@
|
|||
|
||||
+ (id) sharedSOGoWebAuthenticator;
|
||||
|
||||
- (BOOL) checkLogin: (NSString *) _login
|
||||
password: (NSString *) _pwd
|
||||
perr: (SOGoPasswordPolicyError *) _perr
|
||||
expire: (int *) _expire
|
||||
grace: (int *) _grace;
|
||||
|
||||
@end
|
||||
|
||||
#endif /* _SOGOWEBAUTHENTICATOR_H__ */
|
||||
#endif /* _SOGOWEBAUTHENTICATOR_H_ */
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/* SOGoWebAuthenticator.m - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2007-2009 Inverse inc.
|
||||
* Copyright (C) 2007-2010 Inverse inc.
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
*
|
||||
|
@ -37,6 +37,7 @@
|
|||
#import <MainUI/SOGoRootPage.h>
|
||||
|
||||
#import "SOGoCASSession.h"
|
||||
#import "SOGoConstants.h"
|
||||
#import "SOGoPermissions.h"
|
||||
#import "SOGoSystemDefaults.h"
|
||||
#import "SOGoUser.h"
|
||||
|
@ -59,11 +60,28 @@
|
|||
- (BOOL) checkLogin: (NSString *) _login
|
||||
password: (NSString *) _pwd
|
||||
{
|
||||
SOGoPasswordPolicyError perr;
|
||||
int expire, grace;
|
||||
|
||||
return [self checkLogin: _login
|
||||
password: _pwd
|
||||
perr: &perr
|
||||
expire: &expire
|
||||
grace: &grace];
|
||||
}
|
||||
|
||||
- (BOOL) checkLogin: (NSString *) _login
|
||||
password: (NSString *) _pwd
|
||||
perr: (SOGoPasswordPolicyError *) _perr
|
||||
expire: (int *) _expire
|
||||
grace: (int *) _grace
|
||||
{
|
||||
SOGoCASSession *session;
|
||||
SOGoSystemDefaults *sd;
|
||||
BOOL rc;
|
||||
SOGoCASSession *session;
|
||||
|
||||
sd = [SOGoSystemDefaults sharedSystemDefaults];
|
||||
|
||||
if ([[sd authenticationType] isEqualToString: @"cas"])
|
||||
{
|
||||
session = [SOGoCASSession CASSessionWithIdentifier: _pwd];
|
||||
|
@ -74,8 +92,15 @@
|
|||
}
|
||||
else
|
||||
rc = [[SOGoUserManager sharedUserManager] checkLogin: _login
|
||||
andPassword: _pwd];
|
||||
password: _pwd
|
||||
perr: _perr
|
||||
expire: _expire
|
||||
grace: _grace];
|
||||
|
||||
[self logWithFormat: @"Checked login with ppolicy enabled: %d %d %d", *_perr, *_expire, *_grace];
|
||||
|
||||
// It's important to return the real value here. The callee will handle
|
||||
// the return code and check for the _perr value.
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/* SQLSource.h - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2009 Inverse inc.
|
||||
* Copyright (C) 2009-2010 Inverse inc.
|
||||
*
|
||||
* Author: Ludovic Marcotte <lmarcotte@inverse.ca>
|
||||
*
|
||||
|
@ -40,6 +40,8 @@
|
|||
|
||||
#include "SQLSource.h"
|
||||
|
||||
#include "SOGoConstants.h"
|
||||
|
||||
/**
|
||||
* The view MUST contain the following columns:
|
||||
*
|
||||
|
@ -162,8 +164,17 @@
|
|||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL) checkLogin: (NSString *) login
|
||||
andPassword: (NSString *) password
|
||||
//
|
||||
// SQL sources don't support right now all the password policy
|
||||
// stuff supported by OpenLDAP (and others). If we want to support
|
||||
// this for SQL sources, we'll have to implement the same
|
||||
// kind of logic in this module.
|
||||
//
|
||||
- (BOOL) checkLogin: (NSString *) _login
|
||||
password: (NSString *) _pwd
|
||||
perr: (SOGoPasswordPolicyError *) _perr
|
||||
expire: (int *) _expire
|
||||
grace: (int *) _grace
|
||||
{
|
||||
EOAdaptorChannel *channel;
|
||||
GCSChannelManager *cm;
|
||||
|
@ -180,7 +191,7 @@
|
|||
sql = [NSString stringWithFormat: (@"SELECT c_password"
|
||||
@" FROM %@"
|
||||
@" WHERE c_uid = '%@'"),
|
||||
[_viewURL gcsTableName], login];
|
||||
[_viewURL gcsTableName], _login];
|
||||
|
||||
ex = [channel evaluateExpressionX: sql];
|
||||
if (!ex)
|
||||
|
@ -193,7 +204,7 @@
|
|||
row = [channel fetchAttributes: attrs withZone: NULL];
|
||||
value = [row objectForKey: @"c_password"];
|
||||
|
||||
rc = [self _isPassword: password equalTo: value];
|
||||
rc = [self _isPassword: _pwd equalTo: value];
|
||||
[channel cancelFetch];
|
||||
}
|
||||
else
|
||||
|
@ -207,6 +218,14 @@
|
|||
return rc;
|
||||
}
|
||||
|
||||
- (BOOL) changePasswordForLogin: (NSString *) login
|
||||
oldPassword: (NSString *) oldPassword
|
||||
newPassword: (NSString *) newPassword
|
||||
perr: (SOGoPasswordPolicyError *) perr
|
||||
{
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSDictionary *) _lookupContactEntry: (NSString *) theID
|
||||
considerEmail: (BOOL) b
|
||||
{
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/* SOGoRootPage.h - this file is part of SOGo
|
||||
*
|
||||
* Copyright (C) 2007 Inverse inc.
|
||||
* Copyright (C) 2007-2010 Inverse inc.
|
||||
*
|
||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
||||
*
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#import <Foundation/NSException.h>
|
||||
#import <Foundation/NSTimeZone.h>
|
||||
#import <Foundation/NSURL.h>
|
||||
#import <Foundation/NSValue.h>
|
||||
|
||||
#import <NGObjWeb/NSException+HTTP.h>
|
||||
#import <NGObjWeb/WOApplication.h>
|
||||
|
@ -39,13 +40,17 @@
|
|||
#import <NGExtensions/NSObject+Logs.h>
|
||||
|
||||
#import <SOGo/NSDictionary+Utilities.h>
|
||||
#import <SOGo/NSDictionary+BSJSONAdditions.h>
|
||||
#import <SOGo/SOGoCache.h>
|
||||
#import <SOGo/SOGoCASSession.h>
|
||||
#import <SOGo/SOGoDomainDefaults.h>
|
||||
#import <SOGo/SOGoSystemDefaults.h>
|
||||
#import <SOGo/SOGoUser.h>
|
||||
#import <SOGo/SOGoUserManager.h>
|
||||
#import <SOGo/SOGoWebAuthenticator.h>
|
||||
|
||||
#import <SOGo/SOGoConstants.h>
|
||||
|
||||
#import "SOGoRootPage.h"
|
||||
|
||||
@interface SOGoRootPage (crashAdditions)
|
||||
|
@ -116,16 +121,36 @@
|
|||
NSString *username, *password, *language;
|
||||
NSArray *supportedLanguages;
|
||||
|
||||
SOGoPasswordPolicyError err;
|
||||
int expire, grace;
|
||||
BOOL b;
|
||||
|
||||
err = PolicyNoError;
|
||||
expire = grace = 0;
|
||||
|
||||
auth = [[WOApplication application]
|
||||
authenticatorInContext: context];
|
||||
request = [context request];
|
||||
username = [request formValueForKey: @"userName"];
|
||||
password = [request formValueForKey: @"password"];
|
||||
language = [request formValueForKey: @"language"];
|
||||
if ([auth checkLogin: username password: password])
|
||||
|
||||
if ((b = [auth checkLogin: username
|
||||
password: password
|
||||
perr: &err
|
||||
expire: &expire
|
||||
grace: &grace])
|
||||
&& (err == PolicyNoError || err == PolicyChangeAfterReset))
|
||||
{
|
||||
[self logWithFormat: @"successful login for user '%@'", username];
|
||||
response = [self responseWith204];
|
||||
|
||||
// We must warn the user that he has to change his password
|
||||
if (err == PolicyChangeAfterReset)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
authCookie = [self _cookieWithUsername: username andPassword: password
|
||||
forAuthenticator: auth];
|
||||
[response addCookie: authCookie];
|
||||
|
@ -141,7 +166,21 @@
|
|||
}
|
||||
else
|
||||
{
|
||||
[self logWithFormat: @"failed login for user '%@'", username];
|
||||
[self logWithFormat: @"Login for user '%@' might not have worked - password policy: %d bound: ", username, err, b];
|
||||
|
||||
if (err == PolicyNoError)
|
||||
{
|
||||
[self logWithFormat: @"failed login for user '%@' due to wrong password", username];
|
||||
}
|
||||
else if (err == PolicyAccountLocked)
|
||||
{
|
||||
// Account has been locked due to too many failures
|
||||
}
|
||||
else if (err == PolicyPasswordExpired)
|
||||
{
|
||||
// The password MUST be changed - we need to ask for the old password and the new one here
|
||||
}
|
||||
|
||||
response = [self responseWithStatus: 403];
|
||||
}
|
||||
|
||||
|
@ -372,4 +411,41 @@
|
|||
return aString;
|
||||
}
|
||||
|
||||
- (WOResponse *) changePasswordAction
|
||||
{
|
||||
NSString *username, *password, *newPassword;
|
||||
SOGoUserManager *um;
|
||||
SOGoPasswordPolicyError error;
|
||||
WOResponse *response;
|
||||
WORequest *request;
|
||||
NSDictionary *message, *jsonError;
|
||||
|
||||
request = [context request];
|
||||
message = [NSMutableDictionary dictionaryWithJSONString: [request contentAsString]];
|
||||
username = [message objectForKey: @"userName"];
|
||||
password = [message objectForKey: @"password"];
|
||||
newPassword = [message objectForKey: @"newPassword"];
|
||||
|
||||
um = [SOGoUserManager sharedUserManager];
|
||||
|
||||
// This will also update the cached password in memcached.
|
||||
if ([um changePasswordForLogin: username
|
||||
oldPassword: password
|
||||
newPassword: newPassword
|
||||
perr: &error])
|
||||
response = [self responseWith204];
|
||||
else
|
||||
{
|
||||
jsonError
|
||||
= [NSDictionary dictionaryWithObject: [NSNumber numberWithInt: error]
|
||||
forKey: @"LDAPPasswordPolicyError"];
|
||||
response = [self responseWithStatus: 403
|
||||
andJSONRepresentation: jsonError];
|
||||
[response setHeader: @"application/json"
|
||||
forKey: @"content-type"];
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
@end /* SOGoRootPage */
|
||||
|
|
|
@ -130,6 +130,11 @@
|
|||
pageName = "SOGoRootPage";
|
||||
actionName = "connect";
|
||||
};
|
||||
changePassword = {
|
||||
protectedBy = "<public>";
|
||||
pageName = "SOGoRootPage";
|
||||
actionName = "changePassword";
|
||||
};
|
||||
GET = { // more or less a hack, see README of dbd
|
||||
protectedBy = "<public>";
|
||||
pageName = "SOGoRootPage";
|
||||
|
|
|
@ -34,9 +34,9 @@
|
|||
><var:if condition="shouldDisplayAdditionalPreferences">
|
||||
<li target="additionalView"><span>
|
||||
<var:string label:value="Additional Parameters"/></span></li>
|
||||
<!-- </var:if -->
|
||||
<!-- ><var:if condition="shouldDisplayPasswordChange"> -->
|
||||
<!-- <li target="passwordView"><span><var:string label:value="Password"/></span></li> -->
|
||||
</var:if
|
||||
><var:if condition="shouldDisplayPasswordChange">
|
||||
<li target="passwordView"><span><var:string label:value="Password"/></span></li>
|
||||
</var:if>
|
||||
</ul>
|
||||
<div id="generalView" class="tab">
|
||||
|
@ -278,22 +278,22 @@
|
|||
</div>
|
||||
</div>
|
||||
</var:if
|
||||
></var:if>
|
||||
<!-- <var:if condition="shouldDisplayPasswordChange"> -->
|
||||
<!-- <div id="passwordView" class="tab"> -->
|
||||
<!-- <label><var:string label:value="New password:" -->
|
||||
<!-- /><input type="text" class="textField" -->
|
||||
<!-- const:enabled="disabled" -->
|
||||
<!-- var:value="newPassword"/></label><br/> -->
|
||||
<!-- <label><var:string label:value="Confirmation:" -->
|
||||
<!-- /><input type="text" class="textField" -->
|
||||
<!-- const:enabled="disabled" -->
|
||||
<!-- var:value="newPasswordConfirmation"/></label><br/> -->
|
||||
<!-- <input type="button" class="button" -->
|
||||
<!-- id="changePasswordBtn" label:value="Change"/> -->
|
||||
<!-- </div> -->
|
||||
<!-- </var:if> -->
|
||||
<var:if condition="shouldDisplayAdditionalPreferences"
|
||||
></var:if
|
||||
><var:if condition="shouldDisplayPasswordChange">
|
||||
<div id="passwordView" class="tab">
|
||||
<p id="passwordFields"><label><var:string label:value="New password:"
|
||||
/><input const:id="newPasswordField" class="textField"
|
||||
type="text" const:value=""/></label><br/>
|
||||
<label><var:string label:value="Confirmation:"
|
||||
/><input const:id="newPasswordConfirmationField" class="textField"
|
||||
type="text" const:value=""/></label><br/>
|
||||
<a href="#" class="button" id="changePasswordBtn"
|
||||
><span><var:string label:value="Change"/></span></a>
|
||||
</p>
|
||||
<p id="passwordError"><!-- space --></p>
|
||||
</div>
|
||||
</var:if
|
||||
><var:if condition="shouldDisplayAdditionalPreferences"
|
||||
><div id="additionalView" class="tab">
|
||||
<var:component className="UIxAdditionalPreferences"/>
|
||||
</div></var:if>
|
||||
|
|
|
@ -272,8 +272,7 @@ String.prototype.base64decode = function() {
|
|||
var enc1, enc2, enc3, enc4;
|
||||
var i = 0;
|
||||
|
||||
var input = this.replace(/[^A-Za-z0-9\+\/\=]/g, "");
|
||||
|
||||
var input = "" + this; // .replace(/[^A-Za-z0-9\+\/\=]/g, "")
|
||||
while (i < input.length) {
|
||||
enc1 = this._base64_keyStr.indexOf(input.charAt(i++));
|
||||
enc2 = this._base64_keyStr.indexOf(input.charAt(i++));
|
||||
|
@ -294,7 +293,7 @@ String.prototype.base64decode = function() {
|
|||
}
|
||||
}
|
||||
|
||||
return output.utf8decode();
|
||||
return output;
|
||||
};
|
||||
|
||||
String.prototype.utf8encode = function() {
|
||||
|
|
|
@ -5,10 +5,6 @@ DIV#preferencesTabs
|
|||
right: 5px;
|
||||
bottom: 5px; }
|
||||
|
||||
DIV#passwordView
|
||||
{ padding-top: 3em;
|
||||
padding-right: 10em; }
|
||||
|
||||
TEXTAREA#signature
|
||||
{ position: absolute;
|
||||
width: 100%;
|
||||
|
@ -107,3 +103,25 @@ TH#activeTableHeader
|
|||
|
||||
TD.activeColumn
|
||||
{ text-align: center; }
|
||||
|
||||
P#passwordFields,
|
||||
P#passwordError
|
||||
{ position: absolute;
|
||||
left: 0px; }
|
||||
|
||||
#passwordFields
|
||||
{ top: 40px;
|
||||
width: 410px;
|
||||
text-align: right; }
|
||||
|
||||
P#passwordError
|
||||
{ top: 92px;
|
||||
width: 307px;
|
||||
text-align: right;
|
||||
margin-left: 40px; }
|
||||
|
||||
P.errorMessage#passwordError
|
||||
{ color: #f00; }
|
||||
|
||||
P.infoMessage#passwordError
|
||||
{ color: #00f; }
|
||||
|
|
|
@ -105,7 +105,7 @@ function onChoiceChanged(event) {
|
|||
_setupEvents(false);
|
||||
}
|
||||
|
||||
function addDefaultEmailAddresses() {
|
||||
function addDefaultEmailAddresses(event) {
|
||||
var defaultAddresses = $("defaultEmailAddresses").value.split(/, */);
|
||||
var addresses = $("autoReplyEmailAddresses").value.trim();
|
||||
|
||||
|
@ -121,6 +121,8 @@ function addDefaultEmailAddresses() {
|
|||
});
|
||||
|
||||
$("autoReplyEmailAddresses").value = addresses.join(", ");
|
||||
|
||||
event.stop();
|
||||
}
|
||||
|
||||
function initPreferences() {
|
||||
|
@ -157,9 +159,13 @@ function initPreferences() {
|
|||
onReplyPlacementListChange ();
|
||||
}
|
||||
|
||||
if ($("addDefaultEmailAddresses"))
|
||||
$("addDefaultEmailAddresses").observe("click",
|
||||
addDefaultEmailAddresses);
|
||||
var button = $("addDefaultEmailAddresses");
|
||||
if (button)
|
||||
button.observe("click", addDefaultEmailAddresses);
|
||||
|
||||
var button = $("changePasswordBtn");
|
||||
if (button)
|
||||
button.observe("click", onChangePasswordClick);
|
||||
|
||||
initSieveFilters();
|
||||
}
|
||||
|
@ -549,4 +555,126 @@ function onComposeMessagesTypeChange(event) {
|
|||
}
|
||||
}
|
||||
|
||||
function onChangePasswordClick(event) {
|
||||
var field = $("newPasswordField");
|
||||
var confirmationField = $("newPasswordConfirmationField");
|
||||
if (field && confirmationField) {
|
||||
var password = field.value;
|
||||
if (password == confirmationField.value) {
|
||||
if (password.length > 0)
|
||||
changePassword(password);
|
||||
else
|
||||
showPasswordMessage(_("Password must not be empty."),
|
||||
"error");
|
||||
}
|
||||
else {
|
||||
showPasswordMessage(_("The passwords do not match."
|
||||
+ " Please try again."),
|
||||
"error");
|
||||
field.focus();
|
||||
field.select();
|
||||
}
|
||||
}
|
||||
event.stop();
|
||||
}
|
||||
|
||||
/* TODO: this method could serve as a basis for a basic text container (for
|
||||
example the log console. */
|
||||
function showPasswordMessage(message, msgType) {
|
||||
var para = $("passwordError");
|
||||
if (para) {
|
||||
if (!msgType)
|
||||
msgType = "error";
|
||||
var typeClass = msgType + "Message";
|
||||
if (!para.typeClass || para.typeClass != typeClass) {
|
||||
if (para.typeClass) {
|
||||
para.removeClassName(para.typeClass);
|
||||
}
|
||||
para.typeClass = typeClass;
|
||||
para.addClassName(typeClass);
|
||||
}
|
||||
if (!para.message || para.message != message) {
|
||||
while (para.lastChild) {
|
||||
para.removeChild(para.lastChild);
|
||||
}
|
||||
if (message) {
|
||||
var sentences = message.split("\n");
|
||||
para.appendChild(document.createTextNode(sentences[0]));
|
||||
for (var i = 1; i < sentences.length; i++) {
|
||||
para.appendChild(document.createElement("br"));
|
||||
para.appendChild(document.createTextNode(sentences[i]));
|
||||
}
|
||||
para.message = message;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function readCookie(name) {
|
||||
var nameEQ = name + "=";
|
||||
var ca = document.cookie.split(';');
|
||||
for(var i=0;i < ca.length;i++) {
|
||||
var c = ca[i];
|
||||
while (c.charAt(0)==' ') c = c.substring(1,c.length);
|
||||
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function changePassword(newPassword) {
|
||||
var loginValues = readLoginCookie();
|
||||
if (loginValues) {
|
||||
var content = Object.toJSON({ userName: loginValues[0],
|
||||
password: loginValues[1],
|
||||
newPassword: newPassword });
|
||||
var url = ApplicationBaseURL + "../changePassword";
|
||||
triggerAjaxRequest(url, changePasswordCallback, loginValues[1], content,
|
||||
{"content-type": "application/json"} );
|
||||
}
|
||||
}
|
||||
|
||||
function changePasswordCallback(http) {
|
||||
if (http.readyState == 4) {
|
||||
if (isHttpStatus204(http.status)) {
|
||||
log("it worked");
|
||||
showPasswordMessage(_("The password was changed successfully."), "info");
|
||||
setLoginCookie(UserLogin, http.callbackData);
|
||||
} else {
|
||||
var error;
|
||||
log("header: " + http.header);
|
||||
switch(http.status) {
|
||||
case 403:
|
||||
if (http.getResponseHeader("content-type") == "application/json") {
|
||||
var jsonResponse = http.responseText.evalJSON(false);
|
||||
var perr = jsonResponse["LDAPPasswordPolicyError"];
|
||||
|
||||
// Normal password change failed
|
||||
if (perr == 65535) {
|
||||
error = _("Password change failed");
|
||||
} else if (perr == 3) {
|
||||
error = _("Password change failed - Permission denied");
|
||||
} else if (perr == 5) {
|
||||
error = _("Password change failed - Insufficient password quality");
|
||||
} else if (perr == 6) {
|
||||
error = _("Password change failed - Password is too short");
|
||||
} else if (perr == 7) {
|
||||
error = _("Password change failed - Password is too young");
|
||||
} else if (perr == 8) {
|
||||
error = _("Password change failed - Password is in history");
|
||||
}
|
||||
} else {
|
||||
error = _("Unhandled error code: ") + http.status;
|
||||
}
|
||||
break;
|
||||
case 404:
|
||||
error = _("Password changing is not supported.");
|
||||
break;
|
||||
default:
|
||||
error = _("Unhandled error code: ") + http.status;
|
||||
}
|
||||
showPasswordMessage(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
document.observe("dom:loaded", initPreferences);
|
||||
|
|
|
@ -1695,6 +1695,34 @@ AIM = {
|
|||
i.onComplete(d.body.innerHTML);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
function readCookie(name) {
|
||||
var nameEQ = name + "=";
|
||||
var ca = document.cookie.split(';');
|
||||
for(var i=0;i < ca.length;i++) {
|
||||
var c = ca[i];
|
||||
while (c.charAt(0)==' ') c = c.substring(1,c.length);
|
||||
if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function readLoginCookie() {
|
||||
var loginValues = null;
|
||||
var cookie = readCookie("0xHIGHFLYxSOGo");
|
||||
if (cookie && cookie.length > 8) {
|
||||
var value = decodeURIComponent(cookie.substr(8));
|
||||
loginValues = value.base64decode().split(":");
|
||||
}
|
||||
|
||||
return loginValues;
|
||||
}
|
||||
|
||||
function setLoginCookie(username, password) {
|
||||
var value = (username + ":" + password).base64encode();
|
||||
var cookieValue = encodeURIComponent("basic " + value);
|
||||
window.alert("0xHIGHFLYxSOGo=" + cookieValue);
|
||||
}
|
||||
|
||||
document.observe("dom:loaded", onLoadHandler);
|
||||
|
|
Loading…
Reference in New Issue