From 15de264967384b03839cd53838020855776af686 Mon Sep 17 00:00:00 2001 From: Francis Lachapelle Date: Fri, 12 Jan 2018 14:47:31 -0500 Subject: [PATCH] Web-based management of email certificate (cont'd) --- SoObjects/Mailer/SOGoMailAccount.m | 63 +++++- SoObjects/SOGo/NSString+Utilities.h | 3 + SoObjects/SOGo/NSString+Utilities.m | 49 ++++- SoObjects/SOGo/SOGoUser.m | 29 ++- SoObjects/SOGo/SOGoUserDefaults.h | 10 + SoObjects/SOGo/SOGoUserDefaults.m | 36 +++- UI/Common/English.lproj/Localizable.strings | 7 + UI/MailPartViewers/UIxMailPartSignedViewer.m | 27 +-- UI/MailerUI/English.lproj/Localizable.strings | 13 +- UI/MailerUI/GNUmakefile.preamble | 10 +- UI/MailerUI/UIxMailAccountActions.m | 84 +++++++- UI/MailerUI/product.plist | 5 + UI/PreferencesUI/UIxJSONPreferences.m | 27 ++- UI/PreferencesUI/UIxPreferences.h | 1 + UI/PreferencesUI/UIxPreferences.m | 183 +++++++++++++----- UI/Templates/MailerUI/UIxMailViewTemplate.wox | 4 +- .../PreferencesUI/UIxAccountEditor.wox | 158 +++++++++------ .../js/Preferences/AccountDialogController.js | 40 ++-- .../js/Preferences/PreferencesController.js | 1 + 19 files changed, 562 insertions(+), 188 deletions(-) diff --git a/SoObjects/Mailer/SOGoMailAccount.m b/SoObjects/Mailer/SOGoMailAccount.m index 095f7a180..f1980e37c 100644 --- a/SoObjects/Mailer/SOGoMailAccount.m +++ b/SoObjects/Mailer/SOGoMailAccount.m @@ -1187,10 +1187,36 @@ static NSString *inboxFolderName = @"INBOX"; - (NSData *) certificate { + NSData *mailCertificate; SOGoUserDefaults *ud; + mailCertificate = nil; ud = [[context activeUser] userDefaults]; - return [[ud stringForKey: @"SOGoMailCertificate"] dataByDecodingBase64]; + + if ([nameInContainer isEqualToString: @"0"]) + { + mailCertificate = [[ud stringForKey: @"SOGoMailCertificate"] dataByDecodingBase64]; + } + else + { + int accountIdx; + NSArray* accounts; + NSDictionary *account, *security; + + accountIdx = [nameInContainer intValue] - 1; + accounts = [ud auxiliaryMailAccounts]; + if ([accounts count] > accountIdx) + { + account = [accounts objectAtIndex: accountIdx]; + security = [account objectForKey: @"security"]; + if (security && [[security objectForKey: @"certificate"] length]) + { + mailCertificate = [[security objectForKey: @"certificate"] dataByDecodingBase64]; + } + } + } + + return mailCertificate; } - (void) setCertificate: (NSData *) theData @@ -1199,10 +1225,39 @@ static NSString *inboxFolderName = @"INBOX"; ud = [[context activeUser] userDefaults]; - if ([theData length]) - [ud setObject: [theData stringByEncodingBase64] forKey: @"SOGoMailCertificate"]; + if ([nameInContainer isEqualToString: @"0"]) + { + if ([theData length]) + [ud setObject: [theData stringByEncodingBase64] forKey: @"SOGoMailCertificate"]; + else + [ud removeObjectForKey: @"SOGoMailCertificate"]; + } else - [ud removeObjectForKey: @"SOGoMailCertificate"]; + { + int accountIdx; + NSArray* accounts; + NSMutableDictionary *account, *security; + + accountIdx = [nameInContainer intValue] - 1; + accounts = [ud auxiliaryMailAccounts]; + if ([accounts count] > accountIdx) + { + account = [accounts objectAtIndex: accountIdx]; + security = [account objectForKey: @"security"]; + if (!security) + { + security = [NSMutableDictionary dictionary]; + [account setObject: security forKey: @"security"]; + } + if ([theData length]) + [security setObject: [theData stringByEncodingBase64] forKey: @"certificate"]; + else + [security removeObjectForKey: @"certificate"]; + [ud setAuxiliaryMailAccounts: accounts]; + } + } + + [ud synchronize]; } diff --git a/SoObjects/SOGo/NSString+Utilities.h b/SoObjects/SOGo/NSString+Utilities.h index 09648a0a2..0886c2cc7 100644 --- a/SoObjects/SOGo/NSString+Utilities.h +++ b/SoObjects/SOGo/NSString+Utilities.h @@ -72,6 +72,9 @@ /* LDAP */ - (BOOL) caseInsensitiveMatches: (NSString *) match; +/* OpenSSL multiline DN */ +- (NSArray *) componentsFromMultilineDN; + #ifndef GNUSTEP_BASE_LIBRARY - (BOOL) boolValue; #endif diff --git a/SoObjects/SOGo/NSString+Utilities.m b/SoObjects/SOGo/NSString+Utilities.m index cd38a1b87..21d2fa621 100644 --- a/SoObjects/SOGo/NSString+Utilities.m +++ b/SoObjects/SOGo/NSString+Utilities.m @@ -277,13 +277,21 @@ static int cssEscapingCount; // // See http://www.hackcraft.net/xmlUnicode/ // -// XML1.0 and XML1.1 allow different characters in different contexts, but for the most part I will only describe the XML1.0 usage, XML1.1 usage is analogous. -// The first definition that is relevant here is that of a Char: -// [2] Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF] /* any Unicode character, excluding the surrogate blocks, FFFE, and FFFF. */ +// XML1.0 and XML1.1 allow different characters in different contexts, +// but for the most part I will only describe the XML1.0 usage, XML1.1 +// usage is analogous. The first definition that is relevant here is +// that of a Char: [2] Char ::= #x9 | #xA | #xD | [#x20-#xD7FF] | +// [#xE000-#xFFFD] | [#x10000-#x10FFFF] /* any Unicode character, +// excluding the surrogate blocks, FFFE, and FFFF. */ // -// This defines which characters can be used in an XML1.0 document. It is clearly very liberal, banning only some of the control characters and the noncharacters U+FFFE and U+FFFF. -// Indeed it is somewhat too liberal in my view since it allows other noncharacters (the code points from U+FDD0 to U+FDEF inclusive and the last 2 code points in each plane, -// from U+1FFFE & U+1FFFF through to U+10FFFE & U+10FFFF, are noncharacters) but the production quoted above allows them. +// This defines which characters can be used in an XML1.0 document. It +// is clearly very liberal, banning only some of the control +// characters and the noncharacters U+FFFE and U+FFFF. Indeed it is +// somewhat too liberal in my view since it allows other noncharacters +// (the code points from U+FDD0 to U+FDEF inclusive and the last 2 +// code points in each plane, from U+1FFFE & U+1FFFF through to +// U+10FFFE & U+10FFFF, are noncharacters) but the production quoted +// above allows them. // - (NSString *) safeString { @@ -613,6 +621,35 @@ static int cssEscapingCount; return [(id)sq evaluateWithObject: self]; } +// +// To decompose the DN extracted from a SSL certificate +// using the XN_FLAG_SEP_MULTILINE flag. +// +- (NSArray *) componentsFromMultilineDN +{ + NSArray *pair; + NSEnumerator *componentsEnum, *rdnComponentsEnum; + NSMutableArray *components; + NSString *component, *pairString; + + components = [NSMutableArray array]; + componentsEnum = [[self componentsSeparatedByString: @"\n"] objectEnumerator]; + while (( component = [componentsEnum nextObject] )) + { + rdnComponentsEnum = [[component componentsSeparatedByString: @" + "] objectEnumerator]; + while (( pairString = [rdnComponentsEnum nextObject] )) + { + pair = [pairString componentsSeparatedByString: @"="]; + if ([pair count] == 2) + [components addObject: [NSArray arrayWithObjects: + [pair objectAtIndex: 0], + [pair objectAtIndex: 1], nil]]; + } + } + + return components; +} + #if LIB_FOUNDATION_LIBRARY - (BOOL) boolValue { diff --git a/SoObjects/SOGo/SOGoUser.m b/SoObjects/SOGo/SOGoUser.m index 72ffa1744..bc7ff7910 100644 --- a/SoObjects/SOGo/SOGoUser.m +++ b/SoObjects/SOGo/SOGoUser.m @@ -624,7 +624,7 @@ { NSString *fullName, *replyTo, *imapLogin, *imapServer, *cImapServer, *signature, *encryption, *scheme, *action, *query, *customEmail, *defaultEmail, *sieveServer; - NSMutableDictionary *mailAccount, *identity, *mailboxes, *receipts, *mailSettings; + NSMutableDictionary *mailAccount, *identity, *mailboxes, *receipts, *security, *mailSettings; NSNumber *port; NSMutableArray *identities, *mails; NSArray *delegators, *delegates; @@ -702,7 +702,7 @@ [mailAccount setObject: port forKey: @"port"]; [mailAccount setObject: encryption forKey: @"encryption"]; - // Sieve server + // 4. Sieve server sieveServer = [self _fetchFieldForUser: @"c_sievehostname"]; if (sieveServer) @@ -710,7 +710,7 @@ [mailAccount setObject: sieveServer forKey: @"sieveServerName"]; } - // Identities + // 5. Identities defaultEmail = [NSString stringWithFormat: @"%@@%@", [self loginInDomain], [self domain]]; default_identity = 0; identities = [NSMutableArray new]; @@ -845,7 +845,7 @@ [mailAccount setObject: identities forKey: @"identities"]; [identities release]; - /* receipts */ + // 6. Receipts if ([_defaults allowUserReceipt]) { receipts = [NSMutableDictionary new]; @@ -865,7 +865,7 @@ [receipts release]; } - /* mailboxes */ + // 7. Mailboxes mailboxes = [NSMutableDictionary new]; [self _migrateFolderSettings]; @@ -880,7 +880,7 @@ [mailAccount setObject: mailboxes forKey: @"specialMailboxes"]; [mailboxes release]; - /* delegates */ + // 8. Delegates delegates = [mailSettings objectForKey: @"DelegateTo"]; if (!delegates) delegates = [NSArray array]; @@ -902,6 +902,23 @@ } [mailAccount setObject: delegates forKey: @"delegates"]; + // 9. Security + if ([[_defaults mailCertificate] length]) + { + security = [NSMutableDictionary new]; + + [security setObject: [NSNumber numberWithBool: YES] forKey: @"hasCertificate"]; + + if ([_defaults mailCertificateAlwaysSign]) + [security setObject: [NSNumber numberWithBool: YES] forKey: @"alwaysSign"]; + + if ([_defaults mailCertificateAlwaysEncrypt]) + [security setObject: [NSNumber numberWithBool: YES] forKey: @"alwaysEncrypt"]; + + [mailAccount setObject: security forKey: @"security"]; + [security release]; + } + [mailAccounts addObject: mailAccount]; [mailAccount release]; } diff --git a/SoObjects/SOGo/SOGoUserDefaults.h b/SoObjects/SOGo/SOGoUserDefaults.h index d52b277d1..2970530ae 100644 --- a/SoObjects/SOGo/SOGoUserDefaults.h +++ b/SoObjects/SOGo/SOGoUserDefaults.h @@ -181,6 +181,16 @@ extern NSString *SOGoWeekStartFirstFullWeek; - (void) setMailUseOutlookStyleReplies: (BOOL) newValue; - (BOOL) mailUseOutlookStyleReplies; +- (void) setMailCertificate: (NSData *) newValue; +- (void) unsetMailCertificate; +- (NSString *) mailCertificate; + +- (void) setMailCertificateAlwaysSign: (BOOL) newValue; +- (BOOL) mailCertificateAlwaysSign; + +- (void) setMailCertificateAlwaysEncrypt: (BOOL) newValue; +- (BOOL) mailCertificateAlwaysEncrypt; + - (void) setAuxiliaryMailAccounts: (NSArray *) newAccounts; - (NSArray *) auxiliaryMailAccounts; diff --git a/SoObjects/SOGo/SOGoUserDefaults.m b/SoObjects/SOGo/SOGoUserDefaults.m index 24c518189..28de36cd0 100644 --- a/SoObjects/SOGo/SOGoUserDefaults.m +++ b/SoObjects/SOGo/SOGoUserDefaults.m @@ -739,6 +739,41 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek"; return [self boolForKey: @"SOGoMailUseOutlookStyleReplies"]; } +- (void) setMailCertificate: (NSData *) newValue +{ + [self setObject: [newValue stringByEncodingBase64] forKey: @"SOGoMailCertificate"]; +} + +- (void) unsetMailCertificate +{ + [self removeObjectForKey: @"SOGoMailCertificate"]; +} + +- (NSString *) mailCertificate +{ + return [self stringForKey: @"SOGoMailCertificate"]; +} + +- (void) setMailCertificateAlwaysSign: (BOOL) newValue +{ + [self setBool: newValue forKey: @"SOGoMailCertificateAlwaysSign"]; +} + +- (BOOL) mailCertificateAlwaysSign +{ + return [self boolForKey: @"SOGoMailCertificateAlwaysSign"]; +} + +- (void) setMailCertificateAlwaysEncrypt: (BOOL) newValue +{ + [self setBool: newValue forKey: @"SOGoMailCertificateAlwaysEncrypt"]; +} + +- (BOOL) mailCertificateAlwaysEncrypt +{ + return [self boolForKey: @"SOGoMailCertificateAlwaysEncrypt"]; +} + - (void) setAuxiliaryMailAccounts: (NSArray *) newAccounts { [self setObject: newAccounts forKey: @"AuxiliaryMailAccounts"]; @@ -838,7 +873,6 @@ NSString *SOGoWeekStartFirstFullWeek = @"FirstFullWeek"; return [self objectForKey: @"SOGoMailLabelsColors"]; } - - (void) setSieveFilters: (NSArray *) newValue { [self setObject: newValue forKey: @"SOGoSieveFilters"]; diff --git a/UI/Common/English.lproj/Localizable.strings b/UI/Common/English.lproj/Localizable.strings index 6eaf6b4ef..17aca8744 100644 --- a/UI/Common/English.lproj/Localizable.strings +++ b/UI/Common/English.lproj/Localizable.strings @@ -114,6 +114,13 @@ "No such user." = "No such user."; "You cannot (un)subscribe to a folder that you own!" = "You cannot (un)subscribe to a folder that you own!"; +/* SMIME Certificate field */ +"countryName" = "Country"; +"organizationName" = "Organization"; +"organizationalUnitName" = "Organizational Unit"; +"commonName" = "Common Name"; +"emailAddress" = "Email Address"; + /* Authentication username */ "Username" = "Username"; diff --git a/UI/MailPartViewers/UIxMailPartSignedViewer.m b/UI/MailPartViewers/UIxMailPartSignedViewer.m index fb9cf2233..45c01eb12 100644 --- a/UI/MailPartViewers/UIxMailPartSignedViewer.m +++ b/UI/MailPartViewers/UIxMailPartSignedViewer.m @@ -31,6 +31,8 @@ #import +#import + #import #import "UIxMailRenderingContext.h" @@ -194,33 +196,12 @@ return validSignature; } -- (NSArray *) componentsForDN: (NSString *) dn -{ - NSArray *pair; - NSEnumerator *componentsEnum; - NSMutableArray *components; - NSString *pairString; - - components = [NSMutableArray array]; - componentsEnum = [[dn componentsSeparatedByString: @"\n"] objectEnumerator]; - while (( pairString = [componentsEnum nextObject] )) - { - pair = [pairString componentsSeparatedByString: @"="]; - if ([pair count] == 2) - [components addObject: [NSArray arrayWithObjects: - [self labelForKey: [pair objectAtIndex: 0]], - [pair objectAtIndex: 1], nil]]; - } - - return components; -} - - (NSDictionary *) certificateForSubject: (NSString *) subject andIssuer: (NSString *) issuer { return [NSDictionary dictionaryWithObjectsAndKeys: - [self componentsForDN: subject], @"subject", - [self componentsForDN: issuer], @"issuer", + [subject componentsFromMultilineDN], @"subject", + [issuer componentsFromMultilineDN], @"issuer", nil]; } diff --git a/UI/MailerUI/English.lproj/Localizable.strings b/UI/MailerUI/English.lproj/Localizable.strings index bd2456689..c2d2e3240 100644 --- a/UI/MailerUI/English.lproj/Localizable.strings +++ b/UI/MailerUI/English.lproj/Localizable.strings @@ -246,19 +246,18 @@ /* Number of selected messages in list */ "selected" = "selected"; -/* SMIME Certificate field */ -"countryName" = "Country"; -"organizationName" = "Organization"; -"organizationalUnitName" = "Organizational Unit"; -"commonName" = "Common Name"; -"emailAddress" = "Email Address"; - /* OpenSSL certificate error - unknown issuer */ "certificate verify error" = "This certificate was signed by an unknown issuer"; /* OpenSSL certificate error - email mismatch */ "digest failure" = "This certificate doesn't match the sender email address"; +/* OpenSSL reading error */ +"Error reading the certificate. Please install a new certificate." = "Error reading the certificate. Please install a new certificate."; + +/* Trying to access a non-existent certificate */ +"No certificate associated to account." = "No certificate associated to account."; + "This Folder" = "This Folder"; /* Label popup menu */ diff --git a/UI/MailerUI/GNUmakefile.preamble b/UI/MailerUI/GNUmakefile.preamble index 854eba29f..a3f41acb8 100644 --- a/UI/MailerUI/GNUmakefile.preamble +++ b/UI/MailerUI/GNUmakefile.preamble @@ -1,4 +1,12 @@ -# compiler flags +ifeq ($(HAS_LIBRARY_gnutls),yes) +ADDITIONAL_CPPFLAGS += -DHAVE_GNUTLS=1 +SOGo_LIBRARIES_DEPEND_UPON += -lgnutls +else +ifeq ($(HAS_LIBRARY_ssl),yes) +ADDITIONAL_CPPFLAGS += -DHAVE_OPENSSL=1 +SOGo_LIBRARIES_DEPEND_UPON += -lcrypto +endif +endif ADDITIONAL_CPPFLAGS += \ -Wall -DCOMPILE_FOR_GSTEP_MAKE=1 \ diff --git a/UI/MailerUI/UIxMailAccountActions.m b/UI/MailerUI/UIxMailAccountActions.m index 65cf4e032..7902d9022 100644 --- a/UI/MailerUI/UIxMailAccountActions.m +++ b/UI/MailerUI/UIxMailAccountActions.m @@ -18,6 +18,14 @@ * Boston, MA 02111-1307, USA. */ +#if defined(HAVE_OPENSSL) || defined(HAVE_GNUTLS) +#include +#include +#include +#include +#endif + +#import #import #import @@ -190,6 +198,75 @@ return [self _performDelegationAction: @selector (removeDelegates:)]; } +- (WOResponse *) certificateAction +{ + NSData *pem; + NSDictionary *data; + WOResponse *response; + + pem = [[self clientObject] certificate]; + + if (pem) + { + BIO *pemBio; + X509 *x; + + OpenSSL_add_all_algorithms(); + ERR_load_crypto_strings(); + pemBio = BIO_new_mem_buf((void *) [pem bytes], [pem length]); + x = PEM_read_bio_X509(pemBio, NULL, 0, NULL); + if (x) + { + BIO *buf; + char p[1024]; + NSString *subject, *issuer; + + memset(p, 0, 1024); + buf = BIO_new(BIO_s_mem()); + X509_NAME_print_ex(buf, X509_get_subject_name(x), 0, + ASN1_STRFLGS_ESC_CTRL | XN_FLAG_SEP_MULTILINE | XN_FLAG_FN_LN); + BIO_read(buf, p, 1024); + subject = [NSString stringWithUTF8String: p]; + BIO_free(buf); + + memset(p, 0, 1024); + buf = BIO_new(BIO_s_mem()); + X509_NAME_print_ex(buf, X509_get_issuer_name(x), 0, + ASN1_STRFLGS_ESC_CTRL | XN_FLAG_SEP_MULTILINE | XN_FLAG_FN_LN); + BIO_read(buf, p, 1024); + issuer = [NSString stringWithUTF8String: p]; + BIO_free(buf); + + data = [NSDictionary dictionaryWithObjectsAndKeys: + [subject componentsFromMultilineDN], @"subject", + [issuer componentsFromMultilineDN], @"issuer", + nil]; + response = [self responseWithStatus: 200 + andJSONRepresentation: data]; + } + else + { + NSLog(@"FATAL: failed to read certificate."); + data = [NSDictionary + dictionaryWithObject: [self labelForKey: @"Error reading the certificate. Please install a new certificate."] + forKey: @"message"]; + response = [self responseWithStatus: 500 + andJSONRepresentation: data]; + } + + BIO_free(pemBio); + X509_free(x); + } + else + { + response = [self responseWithStatus: 404 + andJSONRepresentation: [NSDictionary dictionaryWithObject: [self labelForKey: @"No certificate associated to account."] + forKey: @"message"]]; + } + + return response; +} + - (WOResponse *) importCertificateAction { NSArray *parts; @@ -238,7 +315,6 @@ if (password && pkcs12) { - SOGoUserDefaults *ud; NSData *certificate; certificate = [pkcs12 convertPKCS12ToPEMUsingPassword: password]; @@ -246,9 +322,7 @@ if (!certificate) return [self responseWithStatus: 507]; - ud = [[context activeUser] userDefaults]; [[self clientObject] setCertificate: certificate]; - [ud synchronize]; response = [self responseWith204]; } @@ -258,11 +332,7 @@ - (WOResponse *) removeCertificateAction { - SOGoUserDefaults *ud; - - ud = [[context activeUser] userDefaults]; [[self clientObject] setCertificate: nil]; - [ud synchronize]; return [self responseWith204]; } diff --git a/UI/MailerUI/product.plist b/UI/MailerUI/product.plist index 7c3b02487..886585936 100644 --- a/UI/MailerUI/product.plist +++ b/UI/MailerUI/product.plist @@ -433,6 +433,11 @@ actionClass = "UIxMailAccountActions"; actionName = "removeDelegate"; }; + certificate = { + protectedBy = "View"; + actionClass = "UIxMailAccountActions"; + actionName = "certificate"; + }; importCertificate = { protectedBy = "Change Images And Files"; actionClass = "UIxMailAccountActions"; diff --git a/UI/PreferencesUI/UIxJSONPreferences.m b/UI/PreferencesUI/UIxJSONPreferences.m index 9a10f99cd..d131d672a 100644 --- a/UI/PreferencesUI/UIxJSONPreferences.m +++ b/UI/PreferencesUI/UIxJSONPreferences.m @@ -346,8 +346,30 @@ static SoProduct *preferencesProduct = nil; [locale objectForKey: @"NSShortWeekDayNameArray"], @"shortDays", nil] forKey: @"locale"]; - // We inject our default mail account accounts = [NSMutableArray arrayWithArray: [values objectForKey: @"AuxiliaryMailAccounts"]]; + if ([accounts count]) + { + int i; + NSDictionary *security; + NSMutableDictionary *auxAccount, *limitedSecurity; + + for (i = 0; i < [accounts count]; i++) + { + auxAccount = [accounts objectAtIndex: i]; + security = [auxAccount objectForKey: @"security"]; + if (security) + { + limitedSecurity = [NSMutableDictionary dictionaryWithDictionary: security]; + if ([limitedSecurity objectForKey: @"certificate"]) + { + [limitedSecurity setObject: [NSNumber numberWithBool: YES] forKey: @"hasCertificate"]; + [limitedSecurity removeObjectForKey: @"certificate"]; + } + [auxAccount setObject: limitedSecurity forKey: @"security"]; + } + } + } + // We inject our default mail account account = [[[context activeUser] mailAccounts] objectAtIndex: 0]; if (![account objectForKey: @"receipts"]) { @@ -359,6 +381,9 @@ static SoProduct *preferencesProduct = nil; } if (account) [accounts insertObject: account atIndex: 0]; + [values removeObjectForKey: @"SOGoMailCertificate"]; + [values removeObjectForKey: @"SOGoMailCertificateAlwaysSign"]; + [values removeObjectForKey: @"SOGoMailCertificateAlwaysEncrypt"]; [values setObject: accounts forKey: @"AuxiliaryMailAccounts"]; // Add the domain's default vacation subject if user has not specified a custom subject diff --git a/UI/PreferencesUI/UIxPreferences.h b/UI/PreferencesUI/UIxPreferences.h index 712f3962c..c09395ea0 100644 --- a/UI/PreferencesUI/UIxPreferences.h +++ b/UI/PreferencesUI/UIxPreferences.h @@ -58,6 +58,7 @@ - (BOOL) _isSieveServerAvailable; - (id) _sieveClient; - (NSString *) _vacationTextForTemplate: (NSString *) templateFilePath; +- (void) _updateAuxiliaryAccount: (NSMutableDictionary *) newAccount; @end diff --git a/UI/PreferencesUI/UIxPreferences.m b/UI/PreferencesUI/UIxPreferences.m index a1cd3ada3..61b7dac8e 100644 --- a/UI/PreferencesUI/UIxPreferences.m +++ b/UI/PreferencesUI/UIxPreferences.m @@ -1,6 +1,6 @@ /* UIxPreferences.m - this file is part of SOGo * - * Copyright (C) 2007-2016 Inverse inc. + * Copyright (C) 2007-2018 Inverse inc. * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -63,33 +63,33 @@ static NSArray *reminderValues = nil; reminderItems = [NSArray arrayWithObjects: @"NONE", @"5_MINUTES_BEFORE", - @"10_MINUTES_BEFORE", - @"15_MINUTES_BEFORE", - @"30_MINUTES_BEFORE", - @"45_MINUTES_BEFORE", - @"1_HOUR_BEFORE", - @"2_HOURS_BEFORE", - @"5_HOURS_BEFORE", - @"15_HOURS_BEFORE", - @"1_DAY_BEFORE", - @"2_DAYS_BEFORE", - @"1_WEEK_BEFORE", - nil]; + @"10_MINUTES_BEFORE", + @"15_MINUTES_BEFORE", + @"30_MINUTES_BEFORE", + @"45_MINUTES_BEFORE", + @"1_HOUR_BEFORE", + @"2_HOURS_BEFORE", + @"5_HOURS_BEFORE", + @"15_HOURS_BEFORE", + @"1_DAY_BEFORE", + @"2_DAYS_BEFORE", + @"1_WEEK_BEFORE", + nil]; reminderValues = [NSArray arrayWithObjects: @"NONE", @"-PT5M", - @"-PT10M", - @"-PT15M", - @"-PT30M", - @"-PT45M", - @"-PT1H", - @"-PT2H", - @"-PT5H", - @"-PT15H", - @"-P1D", - @"-P2D", - @"-P1W", - nil]; + @"-PT10M", + @"-PT15M", + @"-PT30M", + @"-PT45M", + @"-PT1H", + @"-PT2H", + @"-PT5H", + @"-PT15H", + @"-P1D", + @"-P2D", + @"-P1W", + nil]; [reminderItems retain]; [reminderValues retain]; @@ -1942,6 +1942,27 @@ static NSArray *reminderValues = nil; } } +// +// Used internally +// +- (void) _extractMainSecurityPreferences: (NSDictionary *) security + inDictionary: (NSMutableDictionary *) target + +{ + NSString *action; + + if ([security isKindOfClass: [NSDictionary class]]) + { + action = [security objectForKey: @"alwaysSign"]; + if (action && [action boolValue]) + [target setObject: @"1" forKey: @"SOGoMailCertificateAlwaysSign"]; + + action = [security objectForKey: @"alwaysEncrypt"]; + if (action && [action boolValue]) + [target setObject: @"1" forKey: @"SOGoMailCertificateAlwaysEncrypt"]; + } +} + // // Used internally // @@ -2029,7 +2050,7 @@ static NSArray *reminderValues = nil; knownKeys = [NSArray arrayWithObjects: @"name", @"serverName", @"port", @"userName", @"password", @"encryption", @"replyTo", @"identities", @"mailboxes", - @"receipts", + @"receipts", @"security", nil]; [knownKeys retain]; } @@ -2055,7 +2076,7 @@ static NSArray *reminderValues = nil; if (valid) { - value = [account objectForKey: @"security"]; + value = [account objectForKey: @"encryption"]; if (value) valid = ([value isKindOfClass: [NSString class]] && ([value isEqualToString: @"none"] @@ -2084,6 +2105,7 @@ static NSArray *reminderValues = nil; && [identities count] > 0) [self _extractMainIdentity: [identities objectAtIndex: 0] inDictionary: target]; [self _extractMainReceiptsPreferences: [account objectForKey: @"receipts"] inDictionary: target]; + [self _extractMainSecurityPreferences: [account objectForKey: @"security"] inDictionary: target]; } } @@ -2092,15 +2114,9 @@ static NSArray *reminderValues = nil; // - (NSArray *) _extractAuxiliaryAccounts: (NSArray *) accounts { - int count, max, oldMax; - NSArray *oldAccounts; + int count, max; NSMutableArray *auxAccounts; - NSDictionary *oldAccount; NSMutableDictionary *account; - NSString *password; - - oldAccounts = [user mailAccounts]; - oldMax = [oldAccounts count]; max = [accounts count]; auxAccounts = [NSMutableArray arrayWithCapacity: max]; @@ -2110,18 +2126,7 @@ static NSArray *reminderValues = nil; account = [accounts objectAtIndex: count]; if ([self _validateAccount: account]) { - password = [account objectForKey: @"password"]; - if (!password) - { - if (count < oldMax) - { - oldAccount = [oldAccounts objectAtIndex: count]; - password = [oldAccount objectForKey: @"password"]; - } - if (!password) - password = @""; - [account setObject: password forKey: @"password"]; - } + [self _updateAuxiliaryAccount: account]; [auxAccounts addObject: account]; } } @@ -2129,6 +2134,61 @@ static NSArray *reminderValues = nil; return auxAccounts; } +- (void) _updateAuxiliaryAccount: (NSMutableDictionary *) newAccount +{ + int count, oldMax; + NSArray *oldAccounts, *comparisonAttributes; + NSDictionary *oldAccount, *oldSecurity; + NSEnumerator *comparisonAttributesList; + NSMutableDictionary *newSecurity; + NSString *comparisonAttribute, *password, *certificate; + + comparisonAttributes = [NSArray arrayWithObjects: @"serverName", @"userName", nil]; + oldAccounts = [user mailAccounts]; + oldAccount = nil; + oldMax = [oldAccounts count]; + + for (count = 1 /* skip system account */; !oldAccount && count < oldMax; count++) + { + oldAccount = [oldAccounts objectAtIndex: count]; + comparisonAttributesList = [comparisonAttributes objectEnumerator]; + while (oldAccount && (comparisonAttribute = [comparisonAttributesList nextObject])) + { + if (![[oldAccount objectForKey: comparisonAttribute] + isEqualToString: [newAccount objectForKey: comparisonAttribute]]) + oldAccount = nil; + } + } + + if (oldAccount) + { + // Use previous password if none is provided + password = [newAccount objectForKey: @"password"]; + if (!password) + password = [oldAccount objectForKey: @"password"]; + if (!password) + password = @""; + [newAccount setObject: password forKey: @"password"]; + + // Keep previous certificate + oldSecurity = [oldAccount objectForKey: @"security"]; + if (oldSecurity) + { + certificate = [oldSecurity objectForKey: @"certificate"]; + if (certificate) + { + newSecurity = [newAccount objectForKey: @"security"]; + if (!newSecurity) + { + newSecurity = [NSMutableDictionary dictionary]; + [newAccount setObject: newSecurity forKey: @"security"]; + } + [newSecurity setObject: certificate forKey: @"certificate"]; + } + } + } +} + // - (void) setMailAccounts: (NSString *) newMailAccounts // { // NSArray *accounts; @@ -2174,9 +2234,23 @@ static NSArray *reminderValues = nil; return (forwardEnabled ? @"true" : @"false"); } -// -// -// +/** + * @api {post} /so/:username/Preferences/save Save user's defaults and settings + * @apiVersion 1.0.0 + * @apiName PostPreferencesSave + * @apiGroup Preferences + * @apiDescription Save user's defaults and settings. + * @apiExample {curl} Example usage: + * curl -i http://localhost/SOGo/so/sogo1/Preferences/save \ + * -H 'Content-Type: application/json' \ + * -d '{ "defaults": { SOGoDayStartTime: "09:00", "SOGoDayEndTime": "18:00" }, \ + * "settings": { Calendar: { ListState: "rise", EventsFilterState: "view_next7" } } }' + * + * @apiParam {Object} [defaults] All attributes for user's defaults + * @apiParam {Object} [settings] All attributes for user's settings + * + * @apiError (Error 500) {Object} error The error message + */ - (id ) saveAction { id results; @@ -2263,6 +2337,17 @@ static NSArray *reminderValues = nil; { if ([accounts count] > 0) { + // The first account is the main system account. The following mapping is required: + // - identities[0].signature => SOGoMailSignature + // - identities[0].email => SOGoMailCustomEmail + // - identities[0].fullName => SOGoMailCustomFullName + // - identities[0].replyTo => SOGoMailReplyTo + // - receipts.receiptAction => SOGoMailReceiptAllow + // - receipts.receiptNonRecipientAction => SOGoMailReceiptNonRecipientAction + // - receipts.receiptOutsideDomainAction => SOGoMailReceiptOutsideDomainAction + // - receipts.receiptAnyAction => SOGoMailReceiptAnyAction + // - security.alwaysSign => SOGoMailCertificateAlwaysSign + // - security.alwaysEncrypt => SOGoMailCertificateAlwaysEncrypt [self _extractMainAccountSettings: [accounts objectAtIndex: 0] inDictionary: v]; if ([self mailAuxiliaryUserAccountsEnabled]) accounts = [self _extractAuxiliaryAccounts: accounts]; diff --git a/UI/Templates/MailerUI/UIxMailViewTemplate.wox b/UI/Templates/MailerUI/UIxMailViewTemplate.wox index 93fce3470..183eb9615 100644 --- a/UI/Templates/MailerUI/UIxMailViewTemplate.wox +++ b/UI/Templates/MailerUI/UIxMailViewTemplate.wox @@ -227,14 +227,14 @@
Subject Name
-
+
Issuer
-
+
diff --git a/UI/Templates/PreferencesUI/UIxAccountEditor.wox b/UI/Templates/PreferencesUI/UIxAccountEditor.wox index c3ec9ae57..3a700e8d5 100644 --- a/UI/Templates/PreferencesUI/UIxAccountEditor.wox +++ b/UI/Templates/PreferencesUI/UIxAccountEditor.wox @@ -180,72 +180,108 @@ - - + + -
- - - -
-
+
+ + + + +

+ +

+ + + + expand_more +
+
+
+
+
+
Subject Name
+
+
+
+
+
+
+
Issuer
+
+
+
+
+
+
+
+
+ + + +
+ +
+ + +
+
+ +
+ +
+ +
+ + + +
+
+
+
+
+ + +
+
+ + + - - - -
- - + +
+ + +
-
- - - - -
- - - -
- - - -
- -
- - -
-
+
diff --git a/UI/WebServerResources/js/Preferences/AccountDialogController.js b/UI/WebServerResources/js/Preferences/AccountDialogController.js index c9ef518ec..335a8f3ae 100644 --- a/UI/WebServerResources/js/Preferences/AccountDialogController.js +++ b/UI/WebServerResources/js/Preferences/AccountDialogController.js @@ -7,9 +7,10 @@ /** * @ngInject */ - AccountDialogController.$inject = ['$mdDialog', '$mdToast', 'FileUploader', 'Dialog', 'sgSettings', 'Account', 'defaults', 'account', 'accountId', 'mailCustomFromEnabled']; - function AccountDialogController($mdDialog, $mdToast, FileUploader, Dialog, Settings, Account, defaults, account, accountId, mailCustomFromEnabled) { - var vm = this; + AccountDialogController.$inject = ['$timeout', '$mdDialog', 'FileUploader', 'Dialog', 'sgSettings', 'Account', 'defaults', 'account', 'accountId', 'mailCustomFromEnabled']; + function AccountDialogController($timeout, $mdDialog, FileUploader, Dialog, Settings, Account, defaults, account, accountId, mailCustomFromEnabled) { + var vm = this, + accountObject = new Account({ id: accountId, security: account.security }); vm.defaultPort = 143; vm.defaults = defaults; @@ -17,7 +18,6 @@ vm.accountId = accountId; vm.customFromIsReadonly = customFromIsReadonly; vm.onBeforeUploadCertificate = onBeforeUploadCertificate; - vm.certificateIsInstalled = certificateIsInstalled; vm.removeCertificate = removeCertificate; vm.importCertificate = importCertificate; vm.cancel = cancel; @@ -29,8 +29,7 @@ else if (vm.account.encryption == "ssl") vm.defaultPort = 993; - if (vm.account.certificateFilename) - vm.certificateFilename = vm.account.certificateFilename; + _loadCertificate(); vm.uploader = new FileUploader({ url: [Settings.activeUser('folderURL') + 'Mail', accountId, 'importCertificate'].join('/'), @@ -41,20 +40,26 @@ vm.certificateFilename = item.file.name; }, onSuccessItem: function(item, response, status, headers) { - var el = angular.element(document.getElementById('accountSecurityContent')); - $mdToast.show( - $mdToast.simple() - .content(l('Success')) - .parent(el) - .position('top right') - .hideDelay(3000)); this.clearQueue(); + $timeout(function() { + _.assign(vm.account, {security: {hasCertificate: true}}); + }); + _loadCertificate(); }, onErrorItem: function(item, response, status, headers) { Dialog.alert(l('Error'), l('An error occurred while importing the certificate. Verify your password.')); } }); + function _loadCertificate() { + if (vm.account.security && vm.account.security.hasCertificate) + accountObject.$certificate().then(function(crt) { + vm.certificate = crt; + }, function() { + delete vm.account.security.hasCertificate; + }); + } + function filterByExtension(item) { var isP12File = item.type.indexOf('pkcs12') > 0 || /\.(p12|pfx)$/.test(item.name); vm.form.certificateFilename.$setValidity('fileformat', isP12File); @@ -69,7 +74,7 @@ function importCertificate() { vm.uploader.queue[0].formData = [{ password: vm.certificatePassword }]; - vm.uploader.uploadAll(); + vm.uploader.uploadItem(0); } function onBeforeUploadCertificate(form) { @@ -77,14 +82,9 @@ vm.uploader.clearQueue(); } - function certificateIsInstalled() { - return vm.certificateFilename && vm.uploader.queue.length === 0; - } - function removeCertificate() { - var accountObject = new Account({ id: accountId }); accountObject.$removeCertificate().then(function() { - delete vm.certificateFilename; + delete vm.account.security.hasCertificate; }); } diff --git a/UI/WebServerResources/js/Preferences/PreferencesController.js b/UI/WebServerResources/js/Preferences/PreferencesController.js index be3cbe473..7140007da 100644 --- a/UI/WebServerResources/js/Preferences/PreferencesController.js +++ b/UI/WebServerResources/js/Preferences/PreferencesController.js @@ -99,6 +99,7 @@ account = _.last(this.preferences.defaults.AuxiliaryMailAccounts); angular.extend(account, { + isNew: true, name: "", identities: [ {