diff --git a/.gitignore b/.gitignore index e20363297..2922d3954 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,6 @@ UI/WebServerResources/scss/.sass-cache/ config.make doc tags +.DS_Store +Tests/package-lock.json +.vscode diff --git a/Documentation/SOGoInstallationGuide.asciidoc b/Documentation/SOGoInstallationGuide.asciidoc index 61008c051..6aec5014b 100644 --- a/Documentation/SOGoInstallationGuide.asciidoc +++ b/Documentation/SOGoInstallationGuide.asciidoc @@ -1694,6 +1694,33 @@ userPasswordPolicy = ( ); ---- +Pre-defined constants can also be used : + +---- +userPasswordPolicy = ( + { + label = "POLICY_MIN_LOWERCASE_LETTER"; + value = 1; + }, + { + label = "POLICY_MIN_UPPERCASE_LETTER"; + value = 1; + }, + { + label = "POLICY_MIN_DIGIT"; + value = 2; + }, + { + label = "POLICY_MIN_SPECIAL_SYMBOLS"; + value = 1; + }, + { + label = "POLICY_MIN_LENGTH"; + value = 8; + } +); +---- + |userPasswordAlgorithm |The default algorithm used for password encryption when changing passwords. Possible values are: `none`, `plain`, `crypt`, `md5`, diff --git a/SoObjects/SOGo/GNUmakefile b/SoObjects/SOGo/GNUmakefile index 2950e5507..199943fcb 100644 --- a/SoObjects/SOGo/GNUmakefile +++ b/SoObjects/SOGo/GNUmakefile @@ -129,6 +129,7 @@ SOGo_OBJC_FILES = \ SOGoUserManager.m \ LDAPSource.m \ LDAPSourceSchema.m \ + SOGoPasswordPolicy.m \ SQLSource.m \ SOGoUserProfile.m \ SOGoSQLUserProfile.m \ diff --git a/SoObjects/SOGo/SOGoPasswordPolicy.h b/SoObjects/SOGo/SOGoPasswordPolicy.h new file mode 100644 index 000000000..252fa7b2b --- /dev/null +++ b/SoObjects/SOGo/SOGoPasswordPolicy.h @@ -0,0 +1,19 @@ +#ifndef SOGO_PASSWORD_POLICY_H +#define SOGO_PASSWORD_POLICY_H + +#import + +@interface SOGoPasswordPolicy : NSObject +{ + +} + ++ (NSArray *) policies; ++ (NSArray *) regexPoliciesWithCount:(NSNumber *) count; ++ (NSArray *) createPasswordPolicyRegex: (NSArray *) userPasswordPolicy; ++ (NSArray *) createPasswordPolicyLabels: (NSArray *) userPasswordPolicy + withTranslations: (NSDictionary *) translations; + +@end + +#endif /* SOGO_PASSWORD_POLICY_H */ \ No newline at end of file diff --git a/SoObjects/SOGo/SOGoPasswordPolicy.m b/SoObjects/SOGo/SOGoPasswordPolicy.m new file mode 100644 index 000000000..377ec80c9 --- /dev/null +++ b/SoObjects/SOGo/SOGoPasswordPolicy.m @@ -0,0 +1,118 @@ +/* SOGoPasswordPolicy.h - this file is part of SOGo + * + * Copyright (C) 2009-2022 Alinto + * + * This file is part of SOGo. + * + * 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 +#import +#import +#import + +#import "SOGoPasswordPolicy.h" + +static const NSString *POLICY_MIN_LOWERCASE_LETTER = @"POLICY_MIN_LOWERCASE_LETTER"; +static const NSString *POLICY_MIN_UPPERCASE_LETTER = @"POLICY_MIN_UPPERCASE_LETTER"; +static const NSString *POLICY_MIN_DIGIT = @"POLICY_MIN_DIGIT"; +static const NSString *POLICY_MIN_SPECIAL_SYMBOLS = @"POLICY_MIN_SPECIAL_SYMBOLS"; +static const NSString *POLICY_MIN_LENGTH = @"POLICY_MIN_LENGTH"; + +@implementation SOGoPasswordPolicy + +- (id) init +{ + return [super init]; +} + +- (void) dealloc +{ + [super dealloc]; +} + ++ (NSArray *) policies { + return [NSArray arrayWithObjects: POLICY_MIN_LOWERCASE_LETTER, + POLICY_MIN_UPPERCASE_LETTER, + POLICY_MIN_DIGIT, + POLICY_MIN_SPECIAL_SYMBOLS, + POLICY_MIN_LENGTH, + nil]; +} + ++ (NSArray *) regexPoliciesWithCount:(NSNumber *) count { + return [NSArray arrayWithObjects: [NSString stringWithFormat:@"(.*[a-z].*){%i}", [count intValue]], + [NSString stringWithFormat:@"(.*[A-Z].*){%i}", [count intValue]], + [NSString stringWithFormat:@"(.*[0-9].*){%i}", [count intValue]], + [NSString stringWithFormat:@"([%$&*(){}!?\\@#].*){%i,}", [count intValue]], + [NSString stringWithFormat:@".{%i,}", [count intValue]], + nil]; +} + ++ (NSArray *) createPasswordPolicyRegex: (NSArray *) userPasswordPolicy +{ + NSMutableArray *passwordPolicy = [[NSMutableArray alloc] init]; + [passwordPolicy autorelease]; + for (NSDictionary *policy in userPasswordPolicy) { + NSString *label = [policy objectForKey:@"label"]; + if ([[self policies] containsObject: label]) { + NSNumber *value = [policy objectForKey:@"value"]; + NSInteger index = [[self policies] indexOfObject: label]; + + if (0 < value) { + NSMutableDictionary *newPolicy = [NSMutableDictionary dictionaryWithDictionary: policy]; + [newPolicy setObject:[[self regexPoliciesWithCount: value] objectAtIndex: index] forKey:@"regex"]; + [passwordPolicy addObject: newPolicy]; + } else { + // Do nothing + } + } else { + [passwordPolicy addObject: policy]; + } + } + return passwordPolicy; +} + ++ (NSArray *) createPasswordPolicyLabels: (NSArray *) userPasswordPolicy + withTranslations: (NSDictionary *) translations +{ + NSMutableArray *userTranslatedPasswordPolicy = [[NSMutableArray alloc] init]; + [userTranslatedPasswordPolicy autorelease]; + for (NSDictionary *policy in userPasswordPolicy) { + NSString *label = [policy objectForKey:@"label"]; + if ([[self policies] containsObject: label]) { + NSNumber *value = [policy objectForKey:@"value"]; + if (0 < value) { + NSString *newLabel = [[translations objectForKey: label] + stringByReplacingOccurrencesOfString: @"%{0}" + withString: [value stringValue]]; + [userTranslatedPasswordPolicy addObject:[NSDictionary dictionaryWithObjectsAndKeys: + newLabel, @"label", + [policy objectForKey:@"regex"], @"regex", + nil]]; + } else { + // Do nothing + } + } else { + [userTranslatedPasswordPolicy addObject: policy]; + } + } + + return userTranslatedPasswordPolicy; +} + +@end diff --git a/SoObjects/SOGo/SQLSource.m b/SoObjects/SOGo/SQLSource.m index 3dd14ce31..6d15b004b 100644 --- a/SoObjects/SOGo/SQLSource.m +++ b/SoObjects/SOGo/SQLSource.m @@ -44,6 +44,7 @@ #import "NSString+Crypto.h" #import "SQLSource.h" +#import "SOGoPasswordPolicy.h" /** * The view MUST contain the following columns: @@ -145,7 +146,7 @@ ASSIGN(_authenticationFilter, [udSource objectForKey: @"authenticationFilter"]); ASSIGN(_loginFields, [udSource objectForKey: @"LoginFieldNames"]); ASSIGN(_mailFields, [udSource objectForKey: @"MailFieldNames"]); - ASSIGN(_userPasswordPolicy, [udSource objectForKey: @"userPasswordPolicy"]); + ASSIGN(_userPasswordPolicy, [SOGoPasswordPolicy createPasswordPolicyRegex: [udSource objectForKey: @"userPasswordPolicy"]]); ASSIGN(_userPasswordAlgorithm, [udSource objectForKey: @"userPasswordAlgorithm"]); ASSIGN(_keyPath, [udSource objectForKey: @"keyPath"]); ASSIGN(_imapLoginField, [udSource objectForKey: @"IMAPLoginFieldName"]); diff --git a/UI/PreferencesUI/English.lproj/Localizable.strings b/UI/PreferencesUI/English.lproj/Localizable.strings index f2212ec62..472a7ddf4 100644 --- a/UI/PreferencesUI/English.lproj/Localizable.strings +++ b/UI/PreferencesUI/English.lproj/Localizable.strings @@ -257,6 +257,12 @@ "Confirmation" = "Confirmation"; "Change" = "Change"; "Passwords don't match" = "Passwords don't match"; +"POLICY_MIN_LOWERCASE_LETTER" = "Minimum of %{0} lowercase letter"; +"POLICY_MIN_UPPERCASE_LETTER" = "Minimum of %{0} uppercase letter"; +"POLICY_MIN_DIGIT" = "Minimum of %{0} digit"; +"POLICY_MIN_SPECIAL_SYMBOLS" = "Minimum of %{0}special symbols"; +"POLICY_MIN_LENGTH" = "Minimum length of %{0} characters"; + /* Event+task classifications */ "Default events classification" = "Default events classification"; diff --git a/UI/PreferencesUI/French.lproj/Localizable.strings b/UI/PreferencesUI/French.lproj/Localizable.strings index 2c4c7e1ba..35b4e7304 100644 --- a/UI/PreferencesUI/French.lproj/Localizable.strings +++ b/UI/PreferencesUI/French.lproj/Localizable.strings @@ -257,6 +257,11 @@ "Confirmation" = "Confirmation"; "Change" = "Changer"; "Passwords don't match" = "Les mots de passe ne correspondent pas"; +"POLICY_MIN_LOWERCASE_LETTER" = "Au moins %{0} lettre(s) minuscule(s)"; +"POLICY_MIN_UPPERCASE_LETTER" = "Au moins %{0} lettre(s) majuscule(s)"; +"POLICY_MIN_DIGIT" = "Au moins %{0} chiffre(s)"; +"POLICY_MIN_SPECIAL_SYMBOLS" = "Au moins %{0} caractère(s) special(aux)"; +"POLICY_MIN_LENGTH" = "Longueur d'au moins %{0} caractère(s)"; /* Event+task classifications */ "Default events classification" = "Classification par défaut des événements"; diff --git a/UI/PreferencesUI/UIxPreferences.m b/UI/PreferencesUI/UIxPreferences.m index 439fe9922..174596ab8 100644 --- a/UI/PreferencesUI/UIxPreferences.m +++ b/UI/PreferencesUI/UIxPreferences.m @@ -45,6 +45,7 @@ #import #import #import +#import #import #import @@ -257,9 +258,21 @@ static NSArray *reminderValues = nil; - (NSArray *) passwordPolicy { NSObject *userSource; - + NSMutableDictionary *translations = [[NSMutableDictionary alloc] init]; + NSDictionary *policy; + NSDictionary *translatedUserPolicy; + userSource = [user authenticationSource]; - return [userSource userPasswordPolicy]; + + for(policy in [userSource userPasswordPolicy]) { + [translations setObject:[self labelForKey:[policy objectForKey:@"label"]] + forKey: [policy objectForKey:@"label"]]; + } + translatedUserPolicy = [SOGoPasswordPolicy createPasswordPolicyLabels: [userSource userPasswordPolicy] + withTranslations: translations]; + [translations release]; + + return translatedUserPolicy; } //